From d49020ef898b8d40bcfc67f15d29ad1a5deb5d9e Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Thu, 11 Jan 2024 15:43:55 +0200 Subject: [PATCH 001/408] 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 002/408] 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 003/408] 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 004/408] 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 e91b2c7ac01e8760fe666b9ee7e1551c0bb3d5fc Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Wed, 17 Jan 2024 11:33:37 +0400 Subject: [PATCH 005/408] 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 006/408] 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 007/408] 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 008/408] 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 15e827e69c753ab3bac9a79c416a8fdba86ccea6 Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Sat, 20 Jan 2024 20:25:21 +0400 Subject: [PATCH 009/408] 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 010/408] 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 a22d4ca3366b9a4c78d7e24f15f751861f7fdd95 Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Tue, 23 Jan 2024 21:13:49 +0400 Subject: [PATCH 011/408] 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 a44be8f21866ea9605c17482711137e2108aaca3 Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Thu, 25 Jan 2024 00:20:12 +0400 Subject: [PATCH 012/408] 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 013/408] 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 3d03945a7180a6554945213a3c4b2f2112f13f3d Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Thu, 25 Jan 2024 13:20:43 +0400 Subject: [PATCH 014/408] 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 4377c42b90d5ad1ba0e6d7616cee4eedd50f15e1 Mon Sep 17 00:00:00 2001 From: Kirill Fedoseev Date: Thu, 25 Jan 2024 14:05:45 +0400 Subject: [PATCH 015/408] 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 139cfc926b1cffe11c539f1d2c890863d816906e Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Mon, 29 Jan 2024 01:26:31 +0300 Subject: [PATCH 016/408] 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 017/408] 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 018/408] 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 019/408] 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 020/408] 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 021/408] 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 022/408] 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 023/408] 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 024/408] 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 025/408] 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 026/408] 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 027/408] 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 028/408] 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 029/408] 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 030/408] 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 031/408] 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} (58%) rename .github/workflows/{publish-docker-image-for-rsk.yml => publish-docker-image-for-rootstock.yml} (57%) 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 58% 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 57% 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 032/408] 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 033/408] 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 034/408] 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 035/408] 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 036/408] 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 037/408] 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 038/408] 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 039/408] 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 040/408] 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 041/408] 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 042/408] 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 043/408] 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 044/408] 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 045/408] 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 046/408] 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 047/408] 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 048/408] 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 049/408] 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 050/408] 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 051/408] 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 052/408] 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 053/408] 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 054/408] 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 055/408] 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 056/408] 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 057/408] 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 058/408] 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 059/408] 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 060/408] 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 061/408] 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 062/408] 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 063/408] 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 064/408] 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 065/408] 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 066/408] 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 067/408] 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 068/408] 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 069/408] 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 070/408] 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 071/408] 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 072/408] 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 073/408] 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 074/408] 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 075/408] 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 076/408] 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 077/408] 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 078/408] 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 079/408] 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 080/408] 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 081/408] 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 082/408] 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 083/408] 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 084/408] 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 085/408] 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 086/408] 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 087/408] 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 088/408] 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 089/408] 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 090/408] 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 091/408] 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 092/408] 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 093/408] 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 094/408] 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 095/408] 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 096/408] 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 097/408] 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 098/408] 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 099/408] 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 100/408] 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 101/408] 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 102/408] 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 103/408] 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 104/408] 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 105/408] 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 106/408] 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 107/408] 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 108/408] 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 109/408] 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 110/408] 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 111/408] 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 112/408] 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 113/408] 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 114/408] 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 115/408] 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 116/408] 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 117/408] 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 118/408] 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 119/408] 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 120/408] 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 121/408] 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 122/408] 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 123/408] 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 124/408] 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 125/408] 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 126/408] 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 127/408] =?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 128/408] 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 129/408] 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 130/408] 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 131/408] 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 132/408] 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 133/408] 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 134/408] 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 135/408] 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 136/408] 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 137/408] 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 138/408] 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 139/408] 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 140/408] 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 141/408] 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 142/408] 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 143/408] 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 144/408] 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 145/408] 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 146/408] 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 147/408] 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 148/408] 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 149/408] 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 150/408] 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 151/408] 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 152/408] 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 153/408] 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 154/408] 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 155/408] 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 156/408] 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 157/408] 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 158/408] 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 159/408] 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 160/408] 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 161/408] 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 162/408] 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 163/408] 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 164/408] 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 165/408] 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 166/408] 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 167/408] 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 168/408] 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 169/408] 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 170/408] 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 171/408] 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 172/408] 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 173/408] 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 174/408] 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 175/408] 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 176/408] 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 177/408] 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 178/408] 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 179/408] 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 180/408] 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 181/408] 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 182/408] 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 183/408] 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 184/408] 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 185/408] 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 186/408] 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 187/408] 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") %> -
## 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 245/408] 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 246/408] 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 247/408] 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 248/408] 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 249/408] 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 250/408] 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 251/408] 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 252/408] 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 253/408] 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 254/408] 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 255/408] 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 256/408] 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 257/408] 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 258/408] 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 259/408] 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 260/408] 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 261/408] 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 262/408] 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 263/408] 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 264/408] 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 265/408] 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 266/408] 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 267/408] 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, From 824e10d166220c9622f9c6320747e00b3376d8f2 Mon Sep 17 00:00:00 2001 From: varasev <33550681+varasev@users.noreply.github.com> Date: Mon, 18 Mar 2024 12:32:10 +0300 Subject: [PATCH 268/408] Remove duplicated tx hashes while indexing OP batches (#9652) * Remove duplicated tx hashes while indexing OP batches * Update changelog * Update changelog --------- Co-authored-by: POA <33550681+poa@users.noreply.github.com> --- CHANGELOG.md | 1 + apps/indexer/lib/indexer/fetcher/optimism/txn_batch.ex | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2073100db10..13af3e339fb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ ### Fixes +- [#9652](https://github.com/blockscout/blockscout/pull/9652) - Remove duplicated tx hashes while indexing OP batches - [#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 diff --git a/apps/indexer/lib/indexer/fetcher/optimism/txn_batch.ex b/apps/indexer/lib/indexer/fetcher/optimism/txn_batch.ex index fa0dfcd371d4..88adfc74dbb8 100644 --- a/apps/indexer/lib/indexer/fetcher/optimism/txn_batch.ex +++ b/apps/indexer/lib/indexer/fetcher/optimism/txn_batch.ex @@ -615,7 +615,7 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do seq = %{ id: frame_sequence_id, - l1_transaction_hashes: Enum.reverse(l1_transaction_hashes), + l1_transaction_hashes: Enum.uniq(Enum.reverse(l1_transaction_hashes)), l1_timestamp: channel.l1_timestamp } From 3d6025b26120f74bcdb687a50d781196930c3753 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Mon, 18 Mar 2024 13:17:13 +0300 Subject: [PATCH 269/408] Send timeout param in debug_traceBlockByNumber request (#9654) --- CHANGELOG.md | 1 + .../lib/ethereum_jsonrpc/geth.ex | 17 +++++++++------- .../test/ethereum_jsonrpc/geth_test.exs | 20 +++++++++---------- config/runtime.exs | 2 +- docker-compose/envs/common-blockscout.env | 1 + 5 files changed, 23 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13af3e339fb5..8109e4205eff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ ### Fixes +- [#9654](https://github.com/blockscout/blockscout/pull/9654) - Send timeout param in debug_traceBlockByNumber request - [#9652](https://github.com/blockscout/blockscout/pull/9652) - Remove duplicated tx hashes while indexing OP batches - [#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` diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex index fbd067d0d446..740b1a216f3e 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex @@ -42,10 +42,9 @@ defmodule EthereumJSONRPC.Geth do end defp correct_timeouts(json_rpc_named_arguments) do - debug_trace_transaction_timeout = - Application.get_env(:ethereum_jsonrpc, __MODULE__)[:debug_trace_transaction_timeout] + debug_trace_timeout = Application.get_env(:ethereum_jsonrpc, __MODULE__)[:debug_trace_timeout] - case CommonHelper.parse_duration(debug_trace_transaction_timeout) do + case CommonHelper.parse_duration(debug_trace_timeout) do {:error, :invalid_format} -> json_rpc_named_arguments @@ -152,21 +151,25 @@ defmodule EthereumJSONRPC.Geth do @tracer File.read!(@tracer_path) defp debug_trace_transaction_request(%{id: id, hash_data: hash_data}) do - debug_trace_transaction_timeout = - Application.get_env(:ethereum_jsonrpc, __MODULE__)[:debug_trace_transaction_timeout] + debug_trace_timeout = Application.get_env(:ethereum_jsonrpc, __MODULE__)[:debug_trace_timeout] request(%{ id: id, method: "debug_traceTransaction", - params: [hash_data, %{timeout: debug_trace_transaction_timeout} |> Map.merge(tracer_params())] + params: [hash_data, %{timeout: debug_trace_timeout} |> Map.merge(tracer_params())] }) end defp debug_trace_block_by_number_request({id, block_number}) do + debug_trace_timeout = Application.get_env(:ethereum_jsonrpc, __MODULE__)[:debug_trace_timeout] + request(%{ id: id, method: "debug_traceBlockByNumber", - params: [integer_to_quantity(block_number), tracer_params()] + params: [ + integer_to_quantity(block_number), + %{timeout: debug_trace_timeout} |> Map.merge(tracer_params()) + ] }) end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs index ed1202cf3b12..61195d8c3234 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs @@ -47,7 +47,7 @@ defmodule EthereumJSONRPC.GethTest do ]} end) - Application.put_env(:ethereum_jsonrpc, Geth, tracer: "js", debug_trace_transaction_timeout: "5s") + Application.put_env(:ethereum_jsonrpc, Geth, tracer: "js", debug_trace_timeout: "5s") assert {:ok, [ @@ -357,11 +357,11 @@ defmodule EthereumJSONRPC.GethTest do ]} end) - Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_transaction_timeout: "5s") + Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_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") + Application.put_env(:ethereum_jsonrpc, Geth, tracer: "js", debug_trace_timeout: "5s") assert call_tracer_internal_txs == Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments) @@ -429,11 +429,11 @@ defmodule EthereumJSONRPC.GethTest do ]} end) - Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_transaction_timeout: "5s") + Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_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") + Application.put_env(:ethereum_jsonrpc, Geth, tracer: "js", debug_trace_timeout: "5s") assert call_tracer_internal_txs == Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments) @@ -469,7 +469,7 @@ defmodule EthereumJSONRPC.GethTest do ]} end) - Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_transaction_timeout: "5s") + Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_timeout: "5s") assert {:ok, [ @@ -522,7 +522,7 @@ defmodule EthereumJSONRPC.GethTest do ]} end) - Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_transaction_timeout: "5s") + Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_timeout: "5s") uppercase_result = Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments) @@ -601,7 +601,7 @@ defmodule EthereumJSONRPC.GethTest do ]} end) - Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_transaction_timeout: "5s") + Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_timeout: "5s") assert {:ok, [ @@ -721,7 +721,7 @@ defmodule EthereumJSONRPC.GethTest do ]} end) - Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_transaction_timeout: "5s") + Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_timeout: "5s") assert {:ok, [ @@ -868,7 +868,7 @@ defmodule EthereumJSONRPC.GethTest do ]} end) - Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_transaction_timeout: "5s") + Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_timeout: "5s") assert Geth.fetch_block_internal_transactions([block_number], json_rpc_named_arguments) == Geth.fetch_internal_transactions( diff --git a/config/runtime.exs b/config/runtime.exs index b1f384bdba1b..39a2dd0945cd 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -164,7 +164,7 @@ config :ethereum_jsonrpc, EthereumJSONRPC.HTTP, 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"), + debug_trace_timeout: System.get_env("ETHEREUM_JSONRPC_DEBUG_TRACE_TRANSACTION_TIMEOUT", "5s"), tracer: if(ConfigHelper.chain_type() == "polygon_edge", do: "polygon_edge", diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index 11d5e7872571..7d4bf2ed2022 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -140,6 +140,7 @@ INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER=false # INDEXER_EMPTY_BLOCKS_SANITIZER_INTERVAL= # INDEXER_INTERNAL_TRANSACTIONS_BATCH_SIZE= # INDEXER_INTERNAL_TRANSACTIONS_CONCURRENCY= +# ETHEREUM_JSONRPC_DEBUG_TRACE_TRANSACTION_TIMEOUT= # INDEXER_BLOCK_REWARD_BATCH_SIZE= # INDEXER_BLOCK_REWARD_CONCURRENCY= # INDEXER_TOKEN_INSTANCE_USE_BASE_URI_RETRY= From c3d3ceee22fd4d1aaa695f4b33056e9dc8f9bf29 Mon Sep 17 00:00:00 2001 From: nikitosing <32202610+nikitosing@users.noreply.github.com> Date: Mon, 18 Mar 2024 19:22:38 +0300 Subject: [PATCH 270/408] Tokens import improvements (#9653) * Tokens import improvements * Fix review comment --- CHANGELOG.md | 1 + .../controllers/api/v2/import_controller.ex | 6 +++--- apps/explorer/lib/explorer/chain.ex | 21 +++++++++++++++---- .../explorer/chain/import/runner/tokens.ex | 5 ++++- .../lib/explorer/token/metadata_retriever.ex | 1 - apps/explorer/test/support/factory.ex | 2 +- apps/indexer/lib/indexer/fetcher/token.ex | 4 +--- .../fetcher/token_total_supply_on_demand.ex | 4 +--- .../lib/indexer/fetcher/token_updater.ex | 4 +--- 9 files changed, 29 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8109e4205eff..ac78d4d26f02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ ### Fixes - [#9654](https://github.com/blockscout/blockscout/pull/9654) - Send timeout param in debug_traceBlockByNumber request +- [#9653](https://github.com/blockscout/blockscout/pull/9653) - Tokens import improvements - [#9652](https://github.com/blockscout/blockscout/pull/9652) - Remove duplicated tx hashes while indexing OP batches - [#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` 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 d8fefb4f2b5c..cc9b147b2c92 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 @@ -2,8 +2,8 @@ defmodule BlockScoutWeb.API.V2.ImportController do use BlockScoutWeb, :controller alias BlockScoutWeb.API.V2.ApiView - alias Explorer.{Chain, Repo} - alias Explorer.Chain.{Data, SmartContract, Token} + alias Explorer.Chain + alias Explorer.Chain.{Data, SmartContract} alias Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand alias Explorer.SmartContract.EthBytecodeDBInterface @@ -35,7 +35,7 @@ defmodule BlockScoutWeb.API.V2.ImportController do |> put_token_string_field(token_symbol, :symbol) |> put_token_string_field(token_name, :name) - case token |> Token.changeset(changeset) |> Repo.update() do + case Chain.update_token(token, changeset, true) do {:ok, _} -> conn |> put_view(ApiView) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 4085da40f5e6..388484361a17 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -3480,7 +3480,7 @@ defmodule Explorer.Chain do query = from( token in Token, - where: token.cataloged == false, + where: token.cataloged == false or is_nil(token.cataloged), select: token.contract_address_hash ) @@ -3745,11 +3745,24 @@ defmodule Explorer.Chain do As part of updating token, an additional record is inserted for naming the address for reference if a name is provided for a token. """ - @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 + @spec update_token(Token.t(), map(), boolean()) :: {:ok, Token.t()} | {:error, Ecto.Changeset.t()} + def update_token(%Token{contract_address_hash: address_hash} = token, params \\ %{}, info_from_admin_panel? \\ false) do + params = + if Map.has_key?(params, :total_supply) do + Map.put(params, :total_supply_updated_at_block, BlockNumber.get_max()) + else + params + end + 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())) + token_changeset = + token + |> Token.changeset(Map.put(filtered_params, :updated_at, DateTime.utc_now())) + |> (&if(token.is_verified_via_admin_panel && !info_from_admin_panel?, + do: &1 |> Changeset.delete_change(:symbol) |> Changeset.delete_change(:name), + else: &1 + )).() address_name_changeset = Address.Name.changeset(%Address.Name{}, Map.put(filtered_params, :address_hash, address_hash)) diff --git a/apps/explorer/lib/explorer/chain/import/runner/tokens.ex b/apps/explorer/lib/explorer/chain/import/runner/tokens.ex index 638b59a71095..31a1686c41aa 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/tokens.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/tokens.ex @@ -119,7 +119,10 @@ defmodule Explorer.Chain.Import.Runner.Tokens do ordered_changes_list = changes_list # brand new tokens start with no holders - |> Stream.map(&Map.put_new(&1, :holder_count, 0)) + # set cataloged: nil, if not set before, to get proper COALESCE result + # if don't set it, cataloged will default to false (as in DB schema) + # and COALESCE in on_conflict will return false + |> Stream.map(fn token -> token |> Map.put_new(:holder_count, 0) |> Map.put_new(:cataloged, nil) end) # Enforce Token ShareLocks order (see docs: sharelocks.md) |> Enum.sort_by(& &1.contract_address_hash) diff --git a/apps/explorer/lib/explorer/token/metadata_retriever.ex b/apps/explorer/lib/explorer/token/metadata_retriever.ex index 059ff46911bb..2ce09b959327 100644 --- a/apps/explorer/lib/explorer/token/metadata_retriever.ex +++ b/apps/explorer/lib/explorer/token/metadata_retriever.ex @@ -178,7 +178,6 @@ defmodule Explorer.Token.MetadataRetriever do token_to_update = Token |> Repo.get_by(contract_address_hash: contract_address_hash) - |> Repo.preload([:contract_address]) set_skip_metadata(token_to_update) end diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index 9953bf1a59ba..0492254958ba 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -707,7 +707,7 @@ defmodule Explorer.Factory do cataloged: true, icon_url: sequence("https://example.com/icon"), fiat_value: 10.1, - is_verified_via_admin_panel: Enum.random([true, false]) + is_verified_via_admin_panel: false } end diff --git a/apps/indexer/lib/indexer/fetcher/token.ex b/apps/indexer/lib/indexer/fetcher/token.ex index 9d4b0dd253f3..cf92773a5aff 100644 --- a/apps/indexer/lib/indexer/fetcher/token.ex +++ b/apps/indexer/lib/indexer/fetcher/token.ex @@ -51,9 +51,7 @@ defmodule Indexer.Fetcher.Token do @impl BufferedTask @decorate trace(name: "fetch", resource: "Indexer.Fetcher.Token.run/2", service: :indexer, tracer: Tracer) def run([token_contract_address], _json_rpc_named_arguments) do - options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}] - - case Chain.token_from_address_hash(token_contract_address, options) do + case Chain.token_from_address_hash(token_contract_address) do {:ok, %Token{} = token} -> catalog_token(token) end diff --git a/apps/indexer/lib/indexer/fetcher/token_total_supply_on_demand.ex b/apps/indexer/lib/indexer/fetcher/token_total_supply_on_demand.ex index b2564e9f424b..fb488acbcfbf 100644 --- a/apps/indexer/lib/indexer/fetcher/token_total_supply_on_demand.ex +++ b/apps/indexer/lib/indexer/fetcher/token_total_supply_on_demand.ex @@ -45,7 +45,6 @@ defmodule Indexer.Fetcher.TokenTotalSupplyOnDemand do token = Token |> Repo.get_by(contract_address_hash: address) - |> Repo.preload([:contract_address]) if is_nil(token.total_supply_updated_at_block) or BlockNumber.get_max() - token.total_supply_updated_at_block > @ttl_in_blocks do @@ -55,8 +54,7 @@ defmodule Indexer.Fetcher.TokenTotalSupplyOnDemand do token_address_hash |> MetadataRetriever.get_total_supply_of() - {:ok, token} = - Chain.update_token(token, Map.put(token_params, :total_supply_updated_at_block, BlockNumber.get_max())) + {:ok, token} = Chain.update_token(token, token_params) Publisher.broadcast(%{token_total_supply: [token]}, :on_demand) :ok diff --git a/apps/indexer/lib/indexer/fetcher/token_updater.ex b/apps/indexer/lib/indexer/fetcher/token_updater.ex index 58c35c6246f0..3a7acba4fc8e 100644 --- a/apps/indexer/lib/indexer/fetcher/token_updater.ex +++ b/apps/indexer/lib/indexer/fetcher/token_updater.ex @@ -79,12 +79,10 @@ defmodule Indexer.Fetcher.TokenUpdater do @doc false def update_metadata(metadata_list) when is_list(metadata_list) do - options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}] - Enum.each(metadata_list, fn %{contract_address_hash: contract_address_hash} = metadata -> {:ok, hash} = Hash.Address.cast(contract_address_hash) - with {:ok, %Token{cataloged: true} = token} <- Chain.token_from_address_hash(hash, options) do + with {:ok, %Token{cataloged: true} = token} <- Chain.token_from_address_hash(hash) do update_metadata(token, metadata) end end) From 6177a972fb649d752bb2abfba146f1bf8ca191a2 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Mon, 18 Mar 2024 22:52:34 +0300 Subject: [PATCH 271/408] Remove debug code --- apps/explorer/test/explorer/account/notifier/summary_test.exs | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/explorer/test/explorer/account/notifier/summary_test.exs b/apps/explorer/test/explorer/account/notifier/summary_test.exs index 37d18f95dc34..ebc82d5cd63b 100644 --- a/apps/explorer/test/explorer/account/notifier/summary_test.exs +++ b/apps/explorer/test/explorer/account/notifier/summary_test.exs @@ -356,9 +356,6 @@ defmodule Explorer.Account.Notifier.SummaryTest do amount = Decimal.div(transfer.amount, decimals) - IO.inspect("Gimme") - IO.inspect(Summary.process(transfer)) - assert Summary.process(transfer) == [ %Summary{ amount: amount, From 329ed6beca7223e5f98c97f1fb7c4528c12f8768 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 20 Mar 2024 09:39:26 +0300 Subject: [PATCH 272/408] Update CHANGELOG, add zksync release workflow --- .github/workflows/release-zksync.yml | 45 ++++++++++++++++++++++++++++ CHANGELOG.md | 2 +- 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/release-zksync.yml diff --git a/.github/workflows/release-zksync.yml b/.github/workflows/release-zksync.yml new file mode 100644 index 000000000000..01bdb9a7f594 --- /dev/null +++ b/.github/workflows/release-zksync.yml @@ -0,0 +1,45 @@ +name: Release for ZkSync + +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 ZkSync + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-zksync:latest, blockscout/blockscout-zksync:${{ 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=zksync \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index ac78d4d26f02..1bcb85cfac47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,7 @@ - [#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 +- [#9407](https://github.com/blockscout/blockscout/pull/9407) - ERC-404 basic support - [#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 @@ -152,7 +153,6 @@ - [#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 From b6807881baa5b3d23f283350f33c55257fa5e27e Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 20 Mar 2024 09:57:44 +0300 Subject: [PATCH 273/408] Allow to manually trigger zksync workflow --- .github/workflows/release-zksync.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release-zksync.yml b/.github/workflows/release-zksync.yml index 01bdb9a7f594..15ed069ca941 100644 --- a/.github/workflows/release-zksync.yml +++ b/.github/workflows/release-zksync.yml @@ -1,6 +1,7 @@ name: Release for ZkSync on: + workflow_dispatch: release: types: [published] From b80c3a2d3dc580ba9d15a4de1f2db86d320b6ddb Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Wed, 20 Mar 2024 12:39:42 +0300 Subject: [PATCH 274/408] Disallow zksync release workflow dispatch --- .github/workflows/publish-docker-image-for-zksync.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/publish-docker-image-for-zksync.yml b/.github/workflows/publish-docker-image-for-zksync.yml index 1b746bf26983..7cb48f0ab8bb 100644 --- a/.github/workflows/publish-docker-image-for-zksync.yml +++ b/.github/workflows/publish-docker-image-for-zksync.yml @@ -1,7 +1,6 @@ name: Zksync publish Docker image on: - workflow_dispatch: push: branches: - production-zksync From d1a81d8bd762ad95f5d89444153a872b9aff4bf9 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Thu, 21 Mar 2024 15:16:37 +0300 Subject: [PATCH 275/408] chore: Use git-cliff changelog generator (#9687) * chore: Use git-cliff changelog generator * Update cliff.toml Co-authored-by: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> * Update cliff.toml Co-authored-by: Fedor Ivanov --------- Co-authored-by: Maxim Filonov <53992153+sl1depengwyn@users.noreply.github.com> Co-authored-by: Fedor Ivanov --- CONTRIBUTING.md | 15 ++++--- PULL_REQUEST_TEMPLATE.md | 1 - cliff.toml | 84 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 9 deletions(-) create mode 100644 cliff.toml diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 122503bc252f..694f0e404236 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,14 +1,13 @@ -## Contributing +# Contributing 1. Fork it ( ) 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Write tests that cover your work 4. Commit your changes (`git commit -am 'Add some feature'`) 5. Push to the branch (`git push origin my-new-feature`) -6. Create a new Pull Request -7. Update CHANGELOG.md with the link to PR and description of the changes +6. Create a new Pull Request. The title of Pull Request should follow [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and should start with `feat:`, `fix:`, `chore:`, `doc:`, `perf:`, `refactor:` prefix. -### General +## General * Commits should be one logical change that still allows all tests to pass. Prefer smaller commits if there could be two levels of logic grouping. The goal is to allow contributors in the future (including your own future self) to determine your reasoning for making changes and to allow them to cherry-pick, patch or port those changes in isolation to other branches or forks. * If during your PR you reveal a pre-existing bug: @@ -18,14 +17,14 @@ 2. Commit the fix for the bug. 3. Continue original PR work. -### Enhancements +## Enhancements Enhancements cover all changes that make users lives better: * [feature requests filed as issues](https://github.com/blockscout/blockscout/labels/enhancement) that impact end-user [contributors](https://github.com/blockscout/blockscout/labels/contributor) and [developers](https://github.com/blockscout/blockscout/labels/developer) * changes to the [architecture](https://github.com/blockscout/blockscout/labels/architecture) that make it easier for contributors (in the GitHub sense), dev-ops, and deployers to maintain and run blockscout -### Bug Fixes +## Bug Fixes For bug fixes, whenever possible, there should be at least 2 commits: @@ -34,7 +33,7 @@ For bug fixes, whenever possible, there should be at least 2 commits: This format ensures that we can run the test to reproduce the original bug without depending on the new code in the fix, which could lead to the test falsely passing. -### Incompatible Changes +## Incompatible Changes Incompatible changes can arise as a side-effect of either Enhancements or Bug Fixes. During Enhancements, incompatible changes can occur because, as an example, in order to support showing end-users new data, the database schema may need to be changed and the index rebuilt from scratch. During bug fixes, incompatible changes can occur because in order to fix a bug, the schema had to change, or how certain internal APIs are called changed. @@ -45,7 +44,7 @@ Incompatible changes can arise as a side-effect of either Enhancements or Bug Fi **NOTE**: A database reset and re-index is required ``` -### Pull Request +## Pull Request There is a [PULL_REQUEST_TEMPLATE.md](PULL_REQUEST_TEMPLATE.md) for this repository, but since it can't fill in the title for you, please follow the following steps when opening a Pull Request before filling in the template: diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index 001deecbdfeb..bfc691bd1d2f 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -21,7 +21,6 @@ ## Checklist for your Pull Request (PR) - - [ ] I added an entry to `CHANGELOG.md` with this PR - [ ] If I added new functionality, I added tests covering it. - [ ] If I fixed a bug, I added a regression test to prevent the bug from silently reappearing again. - [ ] I checked whether I should update the docs and did so by submitting a PR to https://github.com/blockscout/docs diff --git a/cliff.toml b/cliff.toml new file mode 100644 index 000000000000..dd8e87fbd0f1 --- /dev/null +++ b/cliff.toml @@ -0,0 +1,84 @@ +# git-cliff ~ default configuration file +# https://git-cliff.org/docs/configuration +# +# Lines starting with "#" are comments. +# Configuration options are organized into tables and keys. +# See documentation for more information on available options. + +[changelog] +# changelog header +header = """ +# Changelog\n +""" +body = """ +{% if version %}\ + ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} +{% else %}\ + ## Current +{% endif %}\ +{% for group, commits in commits | group_by(attribute="group") %} + ### {{ group | striptags | trim | upper_first }} + {% for commit in commits %} + - {% if commit.scope %}*({{ commit.scope }})* {% endif %}\ + {% if commit.breaking %}[**breaking**] {% endif %}\ + {{ commit.message | upper_first }}\ + {% endfor %} +{% endfor %}\n +""" +# template for the changelog footer +footer = """ + +""" +# remove the leading and trailing s +trim = true +# postprocessors +postprocessors = [ + # { pattern = '', replace = "https://github.com/orhun/git-cliff" }, # replace repository URL +] + +[git] +# parse the commits based on https://www.conventionalcommits.org +conventional_commits = true +# filter out the commits that are not conventional +filter_unconventional = false +# process each line of a commit as an individual commit +split_commits = false +# regex for preprocessing the commit messages +commit_preprocessors = [ + # Replace issue numbers + { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/blockscout/blockscout/issues/${2}))"}, + # Check spelling of the commit with https://github.com/crate-ci/typos + # If the spelling is incorrect, it will be automatically fixed. + #{ pattern = '.*', replace_command = 'typos --write-changes -' }, +] +# regex for parsing and grouping commits +commit_parsers = [ + { message = "(?i)^feat", group = "🚀 Features" }, + { message = "(?i)^fix", group = "🐛 Bug Fixes" }, + { message = "(?i)^doc", group = "📚 Documentation" }, + { message = "(?i)^perf", group = "⚡ Performance" }, + { message = "(?i)^refactor", group = "🚜 Refactor" }, + { message = "(?i)^chore\\(release\\): prepare for", skip = true }, + { message = "(?i)^chore\\(deps.*\\)", skip = true }, + { message = "(?i)^chore\\(pr\\)", skip = true }, + { message = "(?i)^chore\\(pull\\)", skip = true }, + { message = "(?i)^chore|^ci", group = "⚙️ Miscellaneous Tasks" }, + { body = ".*security", group = "🛡️ Security" }, + { message = "^revert", group = "◀️ Revert" }, +] +# protect breaking changes from being skipped due to matching a skipping commit_parser +protect_breaking_commits = false +# filter out the commits that are not matched by commit parsers +filter_commits = true +# regex for matching git tags +# tag_pattern = "v[0-9].*" +# regex for skipping tags +# skip_tags = "" +# regex for ignoring tags +# ignore_tags = "" +# sort the tags topologically +topo_order = false +# sort the commits inside sections by oldest/newest order +sort_commits = "newest" +# limit the number of commits included in the changelog. +# limit_commits = 42 From 2e852a22398cfc489578f93706daf306879198a9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Mar 2024 15:22:29 +0300 Subject: [PATCH 276/408] chore: Bump floki from 0.36.0 to 0.36.1 (#9670) Bumps [floki](https://github.com/philss/floki) from 0.36.0 to 0.36.1. - [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.36.0...v0.36.1) --- updated-dependencies: - dependency-name: floki dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 7248f7832a11..1c8c1c46a169 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.36.0", "544d5dd8a3107f660633226b5805e47c2ac1fabd782fae86e3b22b02849b20f9", [:mix], [], "hexpm", "ab1ca4b1efb0db00df9a8e726524e2c85be88cf65ac092669186e1674d34d74c"}, + "floki": {:hex, :floki, "0.36.1", "712b7f2ba19a4d5a47dfe3e74d81876c95bbcbee44fe551f0af3d2a388abb3da", [:mix], [], "hexpm", "21ba57abb8204bcc70c439b423fc0dd9f0286de67dc82773a14b0200ada0995f"}, "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 6231d730116e05d5448dad8cd52abe8d7e53d66b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Mar 2024 15:23:20 +0300 Subject: [PATCH 277/408] chore: Bump follow-redirects from 1.15.4 to 1.15.6 in /apps/explorer (#9648) Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.4 to 1.15.6. - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.4...v1.15.6) --- updated-dependencies: - dependency-name: follow-redirects dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- 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 6d5940bb3258..b41a9d0db5ad 100644 --- a/apps/explorer/package-lock.json +++ b/apps/explorer/package-lock.json @@ -28,9 +28,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", - "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", @@ -119,9 +119,9 @@ "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==" }, "follow-redirects": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", - "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==" + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==" }, "js-sha3": { "version": "0.8.0", From 79a6b6bff93d00184e112c506b696b5a68b9fb30 Mon Sep 17 00:00:00 2001 From: Snoppy Date: Thu, 21 Mar 2024 21:14:29 +0800 Subject: [PATCH 278/408] chore: fix typos (#9693) --- .../support/fixture/smart_contract/large_smart_contract.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 44f5c8f60721..1beabcf99768 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 @@ -2763,7 +2763,7 @@ contract HomeWork is IHomeWork, ERC721Enumerable, IERC721Metadata, IERC1412 { view returns (address holder, uint256 score, bytes32 key) { - // Get the key and subbmitter holding the current high score. + // Get the key and submitter holding the current high score. key = _highScoreKey; holder = address(bytes20(key)); From d35f2192e74eb5d99880a48363f196c4a330c673 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Mar 2024 17:26:23 +0300 Subject: [PATCH 279/408] chore: Bump bureaucrat from 0.2.9 to 0.2.10 (#9669) Bumps [bureaucrat](https://github.com/api-hogs/bureaucrat) from 0.2.9 to 0.2.10. - [Release notes](https://github.com/api-hogs/bureaucrat/releases) - [Commits](https://github.com/api-hogs/bureaucrat/commits) --- updated-dependencies: - dependency-name: bureaucrat dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index 1c8c1c46a169..71f88b08d297 100644 --- a/mix.lock +++ b/mix.lock @@ -10,7 +10,7 @@ "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, "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"}, + "bureaucrat": {:hex, :bureaucrat, "0.2.10", "b0de157dad540e40007b663b683f716ced21f85ff0591093aadb209ad0d967e1", [: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 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "bc7e5162b911c29c8ebefee87a2c16fbf13821a58f448a8fd024eb6c17fae15c"}, "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.5", "9eeebb394cc9a0f3ae56b813459f990abb0a3dedee1be6b27fdb50301930502f", [:mix], [], "hexpm", "8d7c597c3e4a64c395980882d4bca3cebb8d74197c590dc272cfd3b6a6310578"}, "cbor": {:hex, :cbor, "1.0.1", "39511158e8ea5a57c1fcb9639aaa7efde67129678fee49ebbda780f6f24959b0", [:mix], [], "hexpm", "5431acbe7a7908f17f6a9cd43311002836a34a8ab01876918d8cfb709cd8b6a2"}, From 610a1451035e51cc29df34629b0953a13b23f271 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Thu, 21 Mar 2024 22:30:24 +0300 Subject: [PATCH 280/408] fix: erc-404 type stored in token balances tables (#9700) --- .../explorer/lib/explorer/chain/token/instance.ex | 2 +- apps/explorer/test/explorer/chain/import_test.exs | 1 + apps/indexer/lib/indexer/block/fetcher.ex | 15 ++++++++++++++- .../indexer/transform/address_token_balances.ex | 2 +- .../transform/address_token_balances_test.exs | 7 +++++-- 5 files changed, 22 insertions(+), 5 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/token/instance.ex b/apps/explorer/lib/explorer/chain/token/instance.ex index 3807d3b88bf7..fa90f5f32d90 100644 --- a/apps/explorer/lib/explorer/chain/token/instance.ex +++ b/apps/explorer/lib/explorer/chain/token/instance.ex @@ -353,7 +353,7 @@ defmodule Explorer.Chain.Token.Instance 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") + |> where([ctb], ctb.address_hash == ^address_hash and not is_nil(ctb.token_id) 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], %{ diff --git a/apps/explorer/test/explorer/chain/import_test.exs b/apps/explorer/test/explorer/chain/import_test.exs index 484aca9f17c5..f396faa2dcad 100644 --- a/apps/explorer/test/explorer/chain/import_test.exs +++ b/apps/explorer/test/explorer/chain/import_test.exs @@ -165,6 +165,7 @@ defmodule Explorer.Chain.ImportTest do to_address_hash: "0x515c09c5bba1ed566b02a5b0599ec5d5d0aee73d", token_contract_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", token_type: "ERC-20", + token: %{type: "ERC-20"}, transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5" } ], diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex index 2676ab02cf78..795a86c6a837 100644 --- a/apps/indexer/lib/indexer/block/fetcher.ex +++ b/apps/indexer/lib/indexer/block/fetcher.ex @@ -197,7 +197,9 @@ defmodule Indexer.Block.Fetcher do |> AddressCoinBalances.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}), + token_transfers_with_token = token_transfers_merge_token(token_transfers, tokens), + address_token_balances = + AddressTokenBalances.params_set(%{token_transfers_params: token_transfers_with_token}), transaction_actions = Enum.map(transaction_actions, fn action -> Map.put(action, :data, Map.delete(action.data, :block_number)) end), token_instances = TokenInstances.params_set(%{token_transfers_params: token_transfers}), @@ -674,4 +676,15 @@ defmodule Indexer.Block.Fetcher do {{String.downcase(hash), fetched_coin_balance_block_number}, Map.delete(address_params, :fetched_coin_balance_block_number)} end + + defp token_transfers_merge_token(token_transfers, tokens) do + Enum.map(token_transfers, fn token_transfer -> + token = + Enum.find(tokens, fn token -> + token.contract_address_hash == token_transfer.token_contract_address_hash + end) + + Map.put(token_transfer, :token, token) + end) + end end diff --git a/apps/indexer/lib/indexer/transform/address_token_balances.ex b/apps/indexer/lib/indexer/transform/address_token_balances.ex index af0c37caa7ef..288757d313c4 100644 --- a/apps/indexer/lib/indexer/transform/address_token_balances.ex +++ b/apps/indexer/lib/indexer/transform/address_token_balances.ex @@ -17,7 +17,7 @@ defmodule Indexer.Transform.AddressTokenBalances do to_address_hash: to_address_hash, token_contract_address_hash: token_contract_address_hash, token_ids: token_ids, - token_type: token_type + token: %{type: token_type} }, acc when is_integer(block_number) and is_binary(from_address_hash) and diff --git a/apps/indexer/test/indexer/transform/address_token_balances_test.exs b/apps/indexer/test/indexer/transform/address_token_balances_test.exs index b70e2f84fe09..28b67ac2ffa7 100644 --- a/apps/indexer/test/indexer/transform/address_token_balances_test.exs +++ b/apps/indexer/test/indexer/transform/address_token_balances_test.exs @@ -26,7 +26,8 @@ defmodule Indexer.Transform.AddressTokenBalancesTest do to_address_hash: to_address_hash, token_contract_address_hash: token_contract_address_hash, token_ids: nil, - token_type: "ERC-20" + token_type: "ERC-20", + token: %{type: "ERC-20"} } params_set = AddressTokenBalances.params_set(%{token_transfers_params: [token_transfer_params]}) @@ -49,6 +50,7 @@ defmodule Indexer.Transform.AddressTokenBalancesTest do to_address_hash: to_address_hash, token_contract_address_hash: token_contract_address_hash, token_type: "ERC-721", + token: %{type: "ERC-721"}, token_ids: nil } @@ -78,6 +80,7 @@ defmodule Indexer.Transform.AddressTokenBalancesTest do to_address_hash: to_address_hash, token_contract_address_hash: token_contract_address_hash, token_type: "ERC-721", + token: %{type: "ERC-1155"}, token_ids: [1] } @@ -90,7 +93,7 @@ defmodule Indexer.Transform.AddressTokenBalancesTest do block_number: 1, token_contract_address_hash: "0xe18035bf8712672935fdb4e5e431b1a0183d2dfc", token_id: 1, - token_type: "ERC-721" + token_type: "ERC-1155" } ]) end From b1ee3edeb03694ce8899dd825748e88b7a6abd38 Mon Sep 17 00:00:00 2001 From: Mohammed Zeglam <56762707+mohammedzeglam-pg@users.noreply.github.com> Date: Thu, 21 Mar 2024 21:31:16 +0200 Subject: [PATCH 281/408] refactor: `Enum.count` to `Enum.empty?` (#9666) * refactor Enum.count to Enum.empty?. * use `not` instead of `!` * fix typo. * improve readability. * refactor if negate branching. * use unless in template. * format code. * remove CHANGELOG record. --- .../lib/block_scout_web/notifier.ex | 2 +- .../templates/layout/_topnav.html.eex | 6 +++--- .../templates/transaction/overview.html.eex | 10 +++++----- .../transaction_raw_trace/_card_body.html.eex | 14 ++++++------- .../lib/block_scout_web/views/address_view.ex | 2 +- .../views/api/rpc/contract_view.ex | 6 +++--- apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex | 6 +++--- apps/explorer/lib/explorer/access_helper.ex | 6 +++--- .../lib/explorer/account/custom_abi.ex | 2 +- apps/explorer/lib/explorer/chain.ex | 4 ++-- .../lib/explorer/chain/bridged_token.ex | 6 +++--- .../runner/address/current_token_balances.ex | 6 +++--- .../import/runner/address/token_balances.ex | 6 +++--- .../import/runner/internal_transactions.ex | 12 +++++------ apps/explorer/lib/explorer/chain/search.ex | 2 +- .../exchange_rates/source/coin_market_cap.ex | 4 ++-- .../lib/explorer/tags/address_to_tag.ex | 2 +- .../lib/explorer/token/balance_reader.ex | 6 +++--- apps/indexer/lib/indexer/block/fetcher.ex | 2 +- .../lib/indexer/fetcher/beacon/client.ex | 2 +- .../indexer/fetcher/polygon_zkevm/bridge.ex | 2 +- .../fetcher/token_balance_on_demand.ex | 20 +++++++++---------- .../indexer/transform/transaction_actions.ex | 6 +++--- 23 files changed, 67 insertions(+), 67 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 ca7027ae89aa..40173cb00416 100644 --- a/apps/block_scout_web/lib/block_scout_web/notifier.ex +++ b/apps/block_scout_web/lib/block_scout_web/notifier.ex @@ -433,7 +433,7 @@ defmodule BlockScoutWeb.Notifier do end defp broadcast_transactions_websocket_v2_inner(transactions, default_channel, event) do - if Enum.count(transactions) > 0 do + if not Enum.empty?(transactions) do Endpoint.broadcast(default_channel, event, %{ transactions: transactions }) 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 7df4369402ac..eb7342cf9319 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 @@ -161,19 +161,19 @@ <%= subnetwork_title() %>