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 ede770f023..10c0d44a18 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 @@ -21,6 +21,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do ] import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1, maybe_preload_ens_to_address: 1] + import Explorer.MicroserviceInterfaces.Metadata, only: [maybe_preload_metadata: 1] alias BlockScoutWeb.AccessHelper alias BlockScoutWeb.API.V2.{BlockView, TransactionView, WithdrawalView} @@ -152,7 +153,10 @@ defmodule BlockScoutWeb.API.V2.AddressController do conn |> put_status(200) |> put_view(TransactionView) - |> render(:transactions, %{transactions: transactions |> maybe_preload_ens(), next_page_params: next_page_params}) + |> render(:transactions, %{ + transactions: transactions |> maybe_preload_ens() |> maybe_preload_metadata(), + next_page_params: next_page_params + }) end end @@ -198,7 +202,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do |> put_status(200) |> put_view(TransactionView) |> render(:token_transfers, %{ - token_transfers: token_transfers |> maybe_preload_ens(), + token_transfers: token_transfers |> maybe_preload_ens() |> maybe_preload_metadata(), next_page_params: next_page_params }) end @@ -230,7 +234,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do |> put_status(200) |> put_view(TransactionView) |> render(:token_transfers, %{ - token_transfers: token_transfers |> maybe_preload_ens(), + token_transfers: token_transfers |> maybe_preload_ens() |> maybe_preload_metadata(), next_page_params: next_page_params }) end @@ -263,7 +267,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do |> put_status(200) |> put_view(TransactionView) |> render(:internal_transactions, %{ - internal_transactions: internal_transactions |> maybe_preload_ens(), + internal_transactions: internal_transactions |> maybe_preload_ens() |> maybe_preload_metadata(), next_page_params: next_page_params }) end @@ -286,7 +290,10 @@ defmodule BlockScoutWeb.API.V2.AddressController do conn |> put_status(200) |> put_view(TransactionView) - |> render(:logs, %{logs: logs |> maybe_preload_ens(), next_page_params: next_page_params}) + |> render(:logs, %{ + logs: logs |> maybe_preload_ens() |> maybe_preload_metadata(), + next_page_params: next_page_params + }) end end @@ -303,7 +310,10 @@ defmodule BlockScoutWeb.API.V2.AddressController do conn |> put_status(200) |> put_view(TransactionView) - |> render(:logs, %{logs: logs |> maybe_preload_ens(), next_page_params: next_page_params}) + |> render(:logs, %{ + logs: logs |> maybe_preload_ens() |> maybe_preload_metadata(), + next_page_params: next_page_params + }) end end @@ -405,7 +415,10 @@ defmodule BlockScoutWeb.API.V2.AddressController do conn |> put_status(200) |> put_view(WithdrawalView) - |> render(:withdrawals, %{withdrawals: withdrawals |> maybe_preload_ens(), next_page_params: next_page_params}) + |> render(:withdrawals, %{ + withdrawals: withdrawals |> maybe_preload_ens() |> maybe_preload_metadata(), + next_page_params: next_page_params + }) end end @@ -425,7 +438,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do conn |> put_status(200) |> render(:addresses, %{ - addresses: addresses |> maybe_preload_ens(), + addresses: addresses |> maybe_preload_ens() |> maybe_preload_metadata(), next_page_params: next_page_params, exchange_rate: exchange_rate, total_supply: total_supply diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex index 6f4c51082d..52dd83f797 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 @@ -14,6 +14,7 @@ defmodule BlockScoutWeb.API.V2.BlockController do only: [delete_parameters_from_next_page_params: 1, select_block_type: 1, type_filter_options: 1] import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1] + import Explorer.MicroserviceInterfaces.Metadata, only: [maybe_preload_metadata: 1] alias BlockScoutWeb.API.V2.{TransactionView, WithdrawalView} alias Explorer.Chain @@ -125,7 +126,10 @@ defmodule BlockScoutWeb.API.V2.BlockController do conn |> put_status(200) - |> render(:blocks, %{blocks: blocks |> maybe_preload_ens(), next_page_params: next_page_params}) + |> render(:blocks, %{ + blocks: blocks |> maybe_preload_ens() |> maybe_preload_metadata(), + next_page_params: next_page_params + }) end def transactions(conn, %{"block_hash_or_number" => block_hash_or_number} = params) do @@ -148,7 +152,10 @@ defmodule BlockScoutWeb.API.V2.BlockController do conn |> put_status(200) |> put_view(TransactionView) - |> render(:transactions, %{transactions: transactions |> maybe_preload_ens(), next_page_params: next_page_params}) + |> render(:transactions, %{ + transactions: transactions |> maybe_preload_ens() |> maybe_preload_metadata(), + next_page_params: next_page_params + }) end end @@ -167,7 +174,10 @@ defmodule BlockScoutWeb.API.V2.BlockController do conn |> put_status(200) |> put_view(WithdrawalView) - |> render(:withdrawals, %{withdrawals: withdrawals |> maybe_preload_ens(), next_page_params: next_page_params}) + |> render(:withdrawals, %{ + withdrawals: withdrawals |> maybe_preload_ens() |> maybe_preload_metadata(), + next_page_params: next_page_params + }) end end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/main_page_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/main_page_controller.ex index e6fdbe0996..8a39c3e368 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 @@ -8,6 +8,7 @@ defmodule BlockScoutWeb.API.V2.MainPageController do import BlockScoutWeb.Account.AuthController, only: [current_user: 1] import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1] + import Explorer.MicroserviceInterfaces.Metadata, only: [maybe_preload_metadata: 1] @transactions_options [ necessity_by_association: %{ @@ -34,7 +35,7 @@ defmodule BlockScoutWeb.API.V2.MainPageController do conn |> put_status(200) |> put_view(BlockView) - |> render(:blocks, %{blocks: blocks |> maybe_preload_ens()}) + |> render(:blocks, %{blocks: blocks |> maybe_preload_ens() |> maybe_preload_metadata()}) end def optimism_deposits(conn, _params) do @@ -56,7 +57,7 @@ defmodule BlockScoutWeb.API.V2.MainPageController do conn |> put_status(200) |> put_view(TransactionView) - |> render(:transactions, %{transactions: recent_transactions |> maybe_preload_ens()}) + |> render(:transactions, %{transactions: recent_transactions |> maybe_preload_ens() |> maybe_preload_metadata()}) end def watchlist_transactions(conn, _params) do @@ -67,7 +68,7 @@ defmodule BlockScoutWeb.API.V2.MainPageController do |> put_status(200) |> put_view(TransactionView) |> render(:transactions_watchlist, %{ - transactions: transactions |> maybe_preload_ens(), + transactions: transactions |> maybe_preload_ens() |> maybe_preload_metadata(), watchlist_names: watchlist_names }) 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 e4bdd25d13..5565f003b5 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 @@ -177,7 +177,7 @@ defmodule BlockScoutWeb.API.V2.Proxy.AccountAbstractionController do end address_hash_strings - |> Enum.filter(&(!is_nil(&1))) + |> Enum.reject(&is_nil/1) |> Enum.uniq() |> Enum.map(fn hash_string -> case Chain.string_to_address_hash(hash_string) do @@ -185,7 +185,7 @@ defmodule BlockScoutWeb.API.V2.Proxy.AccountAbstractionController do _ -> nil end end) - |> Enum.filter(&(!is_nil(&1))) + |> Enum.reject(&is_nil/1) end defp replace_address_hashes(response, addresses) do 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 db2b28b5b5..6d21258242 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 @@ -26,6 +26,7 @@ defmodule BlockScoutWeb.API.V2.TokenController do ] import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1] + import Explorer.MicroserviceInterfaces.Metadata, only: [maybe_preload_metadata: 1] action_fallback(BlockScoutWeb.API.V2.FallbackController) @@ -96,7 +97,7 @@ defmodule BlockScoutWeb.API.V2.TokenController do |> put_status(200) |> put_view(TransactionView) |> render(:token_transfers, %{ - token_transfers: token_transfers |> maybe_preload_ens(), + token_transfers: token_transfers |> maybe_preload_ens() |> maybe_preload_metadata(), next_page_params: next_page_params }) end @@ -116,7 +117,7 @@ defmodule BlockScoutWeb.API.V2.TokenController do conn |> put_status(200) |> render(:token_balances, %{ - token_balances: token_balances |> maybe_preload_ens(), + token_balances: token_balances |> maybe_preload_ens() |> maybe_preload_metadata(), next_page_params: next_page_params, token: token }) @@ -239,7 +240,7 @@ defmodule BlockScoutWeb.API.V2.TokenController do |> put_status(200) |> put_view(TransactionView) |> render(:token_transfers, %{ - token_transfers: token_transfers |> maybe_preload_ens(), + token_transfers: token_transfers |> maybe_preload_ens() |> maybe_preload_metadata(), next_page_params: next_page_params }) end @@ -269,7 +270,7 @@ defmodule BlockScoutWeb.API.V2.TokenController do conn |> put_status(200) |> render(:token_balances, %{ - token_balances: token_holders |> maybe_preload_ens(), + token_balances: token_holders |> maybe_preload_ens() |> maybe_preload_metadata(), next_page_params: next_page_params, token: token }) 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 7e6a53c36d..cb623df0ee 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex @@ -25,6 +25,9 @@ defmodule BlockScoutWeb.API.V2.TransactionController do import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1, maybe_preload_ens_to_transaction: 1] + import Explorer.MicroserviceInterfaces.Metadata, + only: [maybe_preload_metadata: 1, maybe_preload_metadata_to_transaction: 1] + alias BlockScoutWeb.AccessHelper alias BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation, as: TransactionInterpretationService alias BlockScoutWeb.Models.TransactionStateHelper @@ -130,7 +133,9 @@ defmodule BlockScoutWeb.API.V2.TransactionController do Chain.preload_token_transfers(transaction, @token_transfers_in_tx_necessity_by_association, @api_true, false) do conn |> put_status(200) - |> render(:transaction, %{transaction: preloaded |> maybe_preload_ens_to_transaction()}) + |> render(:transaction, %{ + transaction: preloaded |> maybe_preload_ens_to_transaction() |> maybe_preload_metadata_to_transaction() + }) end end @@ -158,7 +163,10 @@ defmodule BlockScoutWeb.API.V2.TransactionController do conn |> put_status(200) - |> render(:transactions, %{transactions: transactions |> maybe_preload_ens(), next_page_params: next_page_params}) + |> render(:transactions, %{ + transactions: transactions |> maybe_preload_ens() |> maybe_preload_metadata(), + next_page_params: next_page_params + }) end @doc """ @@ -175,7 +183,10 @@ defmodule BlockScoutWeb.API.V2.TransactionController do conn |> put_status(200) - |> render(:transactions, %{transactions: transactions |> maybe_preload_ens(), items: true}) + |> render(:transactions, %{ + transactions: transactions |> maybe_preload_ens() |> maybe_preload_metadata(), + items: true + }) end @doc """ @@ -206,7 +217,10 @@ defmodule BlockScoutWeb.API.V2.TransactionController do conn |> put_status(200) - |> render(:transactions, %{transactions: transactions |> maybe_preload_ens(), next_page_params: next_page_params}) + |> render(:transactions, %{ + transactions: transactions |> maybe_preload_ens() |> maybe_preload_metadata(), + next_page_params: next_page_params + }) end def execution_node(conn, %{"execution_node_hash_param" => execution_node_hash_string} = params) do @@ -226,7 +240,10 @@ defmodule BlockScoutWeb.API.V2.TransactionController do conn |> put_status(200) - |> render(:transactions, %{transactions: transactions |> maybe_preload_ens(), next_page_params: next_page_params}) + |> render(:transactions, %{ + transactions: transactions |> maybe_preload_ens() |> maybe_preload_metadata(), + next_page_params: next_page_params + }) end end @@ -288,7 +305,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do conn |> put_status(200) |> render(:token_transfers, %{ - token_transfers: token_transfers |> maybe_preload_ens(), + token_transfers: token_transfers |> maybe_preload_ens() |> maybe_preload_metadata(), next_page_params: next_page_params }) end @@ -316,7 +333,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do conn |> put_status(200) |> render(:internal_transactions, %{ - internal_transactions: internal_transactions |> maybe_preload_ens(), + internal_transactions: internal_transactions |> maybe_preload_ens() |> maybe_preload_metadata(), next_page_params: next_page_params }) end @@ -351,7 +368,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do |> put_status(200) |> render(:logs, %{ tx_hash: transaction_hash, - logs: logs |> maybe_preload_ens(), + logs: logs |> maybe_preload_ens() |> maybe_preload_metadata(), next_page_params: next_page_params }) end @@ -405,7 +422,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do conn |> put_status(200) |> render(:transactions_watchlist, %{ - transactions: transactions |> maybe_preload_ens(), + transactions: transactions |> maybe_preload_ens() |> maybe_preload_metadata(), next_page_params: next_page_params, watchlist_names: watchlist_names }) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/withdrawal_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/withdrawal_controller.ex index 4282d16d4f..f9f9e17e9b 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/withdrawal_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/withdrawal_controller.ex @@ -6,6 +6,7 @@ defmodule BlockScoutWeb.API.V2.WithdrawalController do import BlockScoutWeb.PagingHelper, only: [delete_parameters_from_next_page_params: 1] import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1] + import Explorer.MicroserviceInterfaces.Metadata, only: [maybe_preload_metadata: 1] alias Explorer.Chain @@ -21,7 +22,10 @@ defmodule BlockScoutWeb.API.V2.WithdrawalController do conn |> put_status(200) - |> render(:withdrawals, %{withdrawals: withdrawals |> maybe_preload_ens(), next_page_params: next_page_params}) + |> render(:withdrawals, %{ + withdrawals: withdrawals |> maybe_preload_ens() |> maybe_preload_metadata(), + next_page_params: next_page_params + }) end def withdrawals_counters(conn, _params) do diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex index 2839b49439..f7643c6db8 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex @@ -59,20 +59,22 @@ defmodule BlockScoutWeb.API.V2.Helper do "name" => address_name(address), "implementation_name" => implementation_name(address), "is_verified" => verified?(address), - "ens_domain_name" => address.ens_domain_name + "ens_domain_name" => address.ens_domain_name, + "metadata" => address.metadata } end - def address_with_info(%{ens_domain_name: name}, address_hash) do - nil - |> address_with_info(address_hash) - |> Map.put("ens_domain_name", name) - end - def address_with_info(%NotLoaded{}, address_hash) do address_with_info(nil, address_hash) end + def address_with_info(address_info, address_hash) when is_map(address_info) do + nil + |> address_with_info(address_hash) + |> Map.put("ens_domain_name", address_info[:ens_domain_name]) + |> Map.put("metadata", address_info[:metadata]) + end + def address_with_info(nil, nil) do nil end @@ -83,7 +85,9 @@ defmodule BlockScoutWeb.API.V2.Helper do "is_contract" => false, "name" => nil, "implementation_name" => nil, - "is_verified" => nil + "is_verified" => nil, + "ens_domain_name" => nil, + "metadata" => nil } end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/account/api/v2/user_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/account/api/v2/user_controller_test.exs index 18707b600a..a30a415641 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/account/api/v2/user_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/account/api/v2/user_controller_test.exs @@ -158,7 +158,8 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do "private_tags" => [], "public_tags" => [], "watchlist_names" => [], - "ens_domain_name" => nil + "ens_domain_name" => nil, + "metadata" => nil } }} end) @@ -211,7 +212,8 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do "private_tags" => [], "public_tags" => [], "watchlist_names" => [], - "ens_domain_name" => nil + "ens_domain_name" => nil, + "metadata" => nil } }} end) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs index c8af4536a2..0d9ad02cd1 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 @@ -87,7 +87,8 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do "has_token_transfers" => false, "watchlist_address_id" => nil, "has_beacon_chain_withdrawals" => false, - "ens_domain_name" => nil + "ens_domain_name" => nil, + "metadata" => nil } request = get(conn, "/api/v2/addresses/#{Address.checksum(address.hash)}") diff --git a/apps/explorer/lib/explorer/chain/address.ex b/apps/explorer/lib/explorer/chain/address.ex index c00d92f377..259e5ca145 100644 --- a/apps/explorer/lib/explorer/chain/address.ex +++ b/apps/explorer/lib/explorer/chain/address.ex @@ -91,6 +91,7 @@ defmodule Explorer.Chain.Address do field(:token_transfers_count, :integer) field(:gas_used, :integer) field(:ens_domain_name, :string, virtual: true) + field(:metadata, :any, virtual: true) has_one(:smart_contract, SmartContract, references: :hash) has_one(:token, Token, foreign_key: :contract_address_hash, references: :hash) diff --git a/apps/explorer/lib/explorer/chain/address/metadata_preloader.ex b/apps/explorer/lib/explorer/chain/address/metadata_preloader.ex new file mode 100644 index 0000000000..26de38676a --- /dev/null +++ b/apps/explorer/lib/explorer/chain/address/metadata_preloader.ex @@ -0,0 +1,303 @@ +defmodule Explorer.Chain.Address.MetadataPreloader do + @moduledoc """ + Module responsible for preloading metadata (from BENS, Metadata microservices) to addresses. + """ + alias Ecto.Association.NotLoaded + alias Explorer.MicroserviceInterfaces.{BENS, Metadata} + + alias Explorer.Chain.{ + Address, + Address.CurrentTokenBalance, + Block, + InternalTransaction, + Log, + TokenTransfer, + Transaction, + Withdrawal + } + + @type supported_types :: + Address.t() + | Block.t() + | CurrentTokenBalance.t() + | InternalTransaction.t() + | Log.t() + | TokenTransfer.t() + | Transaction.t() + | Withdrawal.t() + + @type supported_input :: [supported_types] | supported_types + + @doc """ + Preloads ENS/metadata to supported entities + """ + @spec maybe_preload_meta(supported_input, module(), (supported_input -> supported_input)) :: supported_input + def maybe_preload_meta(argument, module, function \\ &preload_ens_to_list/1) do + if module.enabled?() do + function.(argument) + else + argument + end + end + + @doc """ + Preloads ENS name to Transaction.t() + """ + @spec preload_ens_to_transaction(Transaction.t()) :: Transaction.t() + def preload_ens_to_transaction(transaction) do + [transaction_with_ens] = preload_ens_to_list([transaction]) + transaction_with_ens + end + + @doc """ + Preloads ENS name to Address.t() + """ + @spec preload_ens_to_address(Address.t()) :: Address.t() + def preload_ens_to_address(address) do + [address_with_ens] = preload_ens_to_list([address]) + address_with_ens + end + + @doc """ + Preloads ENS names to list of supported entities + """ + @spec preload_ens_to_list([supported_types]) :: [supported_types] + def preload_ens_to_list(items) do + address_hash_strings = + items + |> Enum.reduce([], fn item, acc -> + item_to_address_hash_strings(item) ++ acc + end) + |> Enum.uniq() + + case BENS.ens_names_batch_request(address_hash_strings) do + {:ok, result} -> + put_ens_names(result["names"], items) + + _ -> + items + end + end + + @doc """ + Preloads metadata to list of supported entities + """ + @spec preload_metadata_to_list([supported_types]) :: [supported_types] + def preload_metadata_to_list(items) do + address_hash_strings = + items + |> Enum.reduce([], fn item, acc -> + item_to_address_hash_strings(item) ++ acc + end) + |> Enum.uniq() + + case Metadata.get_addresses_tags(address_hash_strings) do + {:ok, result} -> + put_metadata(result["addresses"], items) + + _ -> + items + end + end + + @doc """ + Preloads metadata to Transaction.t() + """ + @spec preload_metadata_to_transaction(Transaction.t()) :: Transaction.t() + def preload_metadata_to_transaction(transaction) do + [transaction_with_metadata] = preload_metadata_to_list([transaction]) + transaction_with_metadata + end + + @doc """ + 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 + Enum.map(list, fn + %{type: "address", ens_info: ens_info} = search_result when not is_nil(ens_info) -> + search_result + + %{type: "address"} = search_result -> + ens_info = search_result[:address_hash] |> BENS.get_address() + Map.put(search_result, :ens_info, ens_info) + + search_result -> + search_result + end) + end + + defp item_to_address_hash_strings(%Transaction{ + to_address_hash: to_address_hash, + created_contract_address_hash: created_contract_address_hash, + from_address_hash: from_address_hash, + token_transfers: token_transfers + }) do + token_transfers_addresses = + case token_transfers do + token_transfers_list when is_list(token_transfers_list) -> + List.flatten(Enum.map(token_transfers_list, &item_to_address_hash_strings/1)) + + _ -> + [] + end + + ([to_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{ + to_address_hash: to_address_hash, + from_address_hash: from_address_hash + }) do + [to_string(to_address_hash), to_string(from_address_hash)] + end + + defp item_to_address_hash_strings(%InternalTransaction{ + to_address_hash: to_address_hash, + from_address_hash: from_address_hash + }) do + [to_string(to_address_hash), to_string(from_address_hash)] + end + + defp item_to_address_hash_strings(%Log{address_hash: address_hash}) do + [to_string(address_hash)] + end + + defp item_to_address_hash_strings(%Withdrawal{address_hash: address_hash}) do + [to_string(address_hash)] + end + + defp item_to_address_hash_strings(%Block{miner_hash: miner_hash}) do + [to_string(miner_hash)] + end + + defp item_to_address_hash_strings(%CurrentTokenBalance{address_hash: address_hash}) do + [to_string(address_hash)] + end + + defp item_to_address_hash_strings({%Address{} = address, _}) do + item_to_address_hash_strings(address) + end + + defp item_to_address_hash_strings(%Address{hash: hash}) do + [to_string(hash)] + end + + defp put_ens_names(names, items) do + Enum.map(items, &put_meta_to_item(&1, names, :ens_domain_name)) + end + + defp put_metadata(names, items) do + Enum.map(items, &put_meta_to_item(&1, names, :metadata)) + end + + defp put_meta_to_item( + %Transaction{ + to_address_hash: to_address_hash, + created_contract_address_hash: created_contract_address_hash, + from_address_hash: from_address_hash + } = tx, + names, + field_to_put_info + ) do + token_transfers = + case tx.token_transfers do + token_transfers_list when is_list(token_transfers_list) -> + Enum.map(token_transfers_list, &put_meta_to_item(&1, names, field_to_put_info)) + + other -> + other + end + + %Transaction{ + tx + | to_address: alter_address(tx.to_address, to_address_hash, names, field_to_put_info), + created_contract_address: + alter_address(tx.created_contract_address, created_contract_address_hash, names, field_to_put_info), + from_address: alter_address(tx.from_address, from_address_hash, names, field_to_put_info), + token_transfers: token_transfers + } + end + + defp put_meta_to_item( + %TokenTransfer{ + to_address_hash: to_address_hash, + from_address_hash: from_address_hash + } = tt, + names, + field_to_put_info + ) do + %TokenTransfer{ + tt + | to_address: alter_address(tt.to_address, to_address_hash, names, field_to_put_info), + from_address: alter_address(tt.from_address, from_address_hash, names, field_to_put_info) + } + end + + defp put_meta_to_item( + %InternalTransaction{ + to_address_hash: to_address_hash, + created_contract_address_hash: created_contract_address_hash, + from_address_hash: from_address_hash + } = tx, + names, + field_to_put_info + ) do + %InternalTransaction{ + tx + | to_address: alter_address(tx.to_address, to_address_hash, names, field_to_put_info), + created_contract_address: + alter_address(tx.created_contract_address, created_contract_address_hash, names, field_to_put_info), + from_address: alter_address(tx.from_address, from_address_hash, names, field_to_put_info) + } + end + + defp put_meta_to_item(%Log{address_hash: address_hash} = log, names, field_to_put_info) do + %Log{log | address: alter_address(log.address, address_hash, names, field_to_put_info)} + end + + defp put_meta_to_item(%Withdrawal{address_hash: address_hash} = withdrawal, names, field_to_put_info) do + %Withdrawal{withdrawal | address: alter_address(withdrawal.address, address_hash, names, field_to_put_info)} + end + + defp put_meta_to_item(%Block{miner_hash: miner_hash} = block, names, field_to_put_info) do + %Block{block | miner: alter_address(block.miner, miner_hash, names, field_to_put_info)} + end + + defp put_meta_to_item( + %CurrentTokenBalance{address_hash: address_hash} = current_token_balance, + names, + field_to_put_info + ) do + %CurrentTokenBalance{ + current_token_balance + | address: alter_address(current_token_balance.address, address_hash, names, field_to_put_info) + } + end + + defp put_meta_to_item({%Address{} = address, count}, names, field_to_put_info) do + {put_meta_to_item(address, names, field_to_put_info), count} + end + + defp put_meta_to_item(%Address{} = address, names, field_to_put_info) do + alter_address(address, address.hash, names, field_to_put_info) + end + + defp alter_address(_, nil, _names, _field) do + nil + end + + defp alter_address(%NotLoaded{}, address_hash, names, field) do + %{field => names[Address.checksum(address_hash)]} + end + + defp alter_address(%Address{} = address, address_hash, names, :ens_domain_name) do + %Address{address | ens_domain_name: names[Address.checksum(address_hash)]} + end + + defp alter_address(%Address{} = address, address_hash, names, :metadata) do + %Address{address | metadata: names[Address.checksum(address_hash)]} + end +end diff --git a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex index 251919f289..027325872d 100644 --- a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex +++ b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex @@ -3,37 +3,21 @@ defmodule Explorer.MicroserviceInterfaces.BENS do Interface to interact with Blockscout ENS microservice """ - alias Ecto.Association.NotLoaded alias Explorer.Chain + alias Explorer.Chain.Address.MetadataPreloader - alias Explorer.Chain.{ - Address, - Address.CurrentTokenBalance, - Block, - InternalTransaction, - Log, - TokenTransfer, - Transaction, - Withdrawal - } + alias Explorer.Chain.{Address, Transaction} alias Explorer.Utility.Microservice alias HTTPoison.Response + require Logger + import Explorer.Chain.Address.MetadataPreloader, only: [maybe_preload_meta: 3] + @post_timeout :timer.seconds(5) @request_error_msg "Error while sending request to BENS microservice" - @typep supported_types :: - Address.t() - | Block.t() - | CurrentTokenBalance.t() - | InternalTransaction.t() - | Log.t() - | TokenTransfer.t() - | Transaction.t() - | Withdrawal.t() - @doc """ Batch request for ENS names via POST {{baseUrl}}/api/v1/:chainId/addresses:batch-resolve-names """ @@ -66,11 +50,17 @@ defmodule Explorer.MicroserviceInterfaces.BENS do end end - @spec get_address(binary()) :: {:error, :disabled | binary() | Jason.DecodeError.t()} | {:ok, any} + @doc """ + Request for ENS name via GET {{baseUrl}}/api/v1/:chainId/addresses/{address_hash} + """ + @spec get_address(binary()) :: map() | nil def get_address(address) do - with :ok <- Microservice.check_enabled(__MODULE__) do - http_get_request(get_address_url(address), nil) - end + result = + with :ok <- Microservice.check_enabled(__MODULE__) do + http_get_request(get_address_url(address), nil) + end + + parse_get_address_response(result) end @doc """ @@ -90,6 +80,15 @@ defmodule Explorer.MicroserviceInterfaces.BENS do end end + @doc """ + Request for ENS name via GET {{baseUrl}}/api/v1/:chainId/domains:lookup + """ + @spec ens_domain_name_lookup(binary()) :: + nil | %{address_hash: binary(), expiry_date: any(), name: any(), names_count: integer()} + def ens_domain_name_lookup(domain) do + domain |> ens_domain_lookup() |> parse_lookup_response() + end + defp http_post_request(url, body) do headers = [{"Content-Type", "application/json"}] @@ -134,8 +133,8 @@ defmodule Explorer.MicroserviceInterfaces.BENS do end end - @spec enabled?() :: boolean - def enabled?, do: Application.get_env(:explorer, __MODULE__)[:enabled] + @spec enabled?() :: boolean() + def enabled?, do: Microservice.check_enabled(__MODULE__) == :ok defp batch_resolve_name_url do "#{addresses_url()}:batch-resolve-names" @@ -166,88 +165,6 @@ defmodule Explorer.MicroserviceInterfaces.BENS do "#{Microservice.base_url(__MODULE__)}/api/v1/#{chain_id}" end - @doc """ - Preload ENS info to list of entities if enabled?() - """ - @spec maybe_preload_ens([supported_types] | supported_types) :: [supported_types] | supported_types - def maybe_preload_ens(argument, function \\ &preload_ens_to_list/1) do - if enabled?() do - function.(argument) - else - argument - end - end - - @spec maybe_preload_ens_info_to_search_results(list()) :: list() - def maybe_preload_ens_info_to_search_results(list) do - maybe_preload_ens(list, &preload_ens_info_to_search_results/1) - end - - @spec maybe_preload_ens_to_transaction(Transaction.t()) :: Transaction.t() - def maybe_preload_ens_to_transaction(transaction) do - maybe_preload_ens(transaction, &preload_ens_to_transaction/1) - end - - @spec preload_ens_to_transaction(Transaction.t()) :: Transaction.t() - def preload_ens_to_transaction(transaction) do - [transaction_with_ens] = preload_ens_to_list([transaction]) - transaction_with_ens - end - - @spec maybe_preload_ens_to_address(Address.t()) :: Address.t() - def maybe_preload_ens_to_address(address) do - maybe_preload_ens(address, &preload_ens_to_address/1) - end - - @spec preload_ens_to_address(Address.t()) :: Address.t() - def preload_ens_to_address(address) do - [address_with_ens] = preload_ens_to_list([address]) - address_with_ens - end - - @doc """ - Preload ENS names to list of entities - """ - @spec preload_ens_to_list([supported_types]) :: [supported_types] - def preload_ens_to_list(items) do - address_hash_strings = - Enum.reduce(items, [], fn item, acc -> - item_to_address_hash_strings(item) ++ acc - end) - - case ens_names_batch_request(address_hash_strings) do - {:ok, result} -> - put_ens_names(result["names"], items) - - _ -> - items - end - end - - @doc """ - Preload ENS info to search result, using get_address/1 - """ - @spec preload_ens_info_to_search_results(list) :: list - def preload_ens_info_to_search_results(list) do - Enum.map(list, fn - %{type: "address", ens_info: ens_info} = search_result when not is_nil(ens_info) -> - search_result - - %{type: "address"} = search_result -> - ens_info = search_result[:address_hash] |> get_address() |> parse_get_address_response() - Map.put(search_result, :ens_info, ens_info) - - search_result -> - search_result - end) - end - - @spec ens_domain_name_lookup(binary()) :: - nil | %{address_hash: binary(), expiry_date: any(), name: any(), names_count: integer()} - def ens_domain_name_lookup(domain) do - domain |> ens_domain_lookup() |> parse_lookup_response() - end - defp parse_lookup_response( {:ok, %{ @@ -293,160 +210,35 @@ defmodule Explorer.MicroserviceInterfaces.BENS do defp parse_get_address_response(_), do: nil - defp item_to_address_hash_strings(%Transaction{ - to_address_hash: to_address_hash, - created_contract_address_hash: created_contract_address_hash, - from_address_hash: from_address_hash, - token_transfers: token_transfers - }) do - token_transfers_addresses = - case token_transfers do - token_transfers_list when is_list(token_transfers_list) -> - List.flatten(Enum.map(token_transfers_list, &item_to_address_hash_strings/1)) - - _ -> - [] - end - - ([to_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{ - to_address_hash: to_address_hash, - from_address_hash: from_address_hash - }) do - [to_string(to_address_hash), to_string(from_address_hash)] - end - - defp item_to_address_hash_strings(%InternalTransaction{ - to_address_hash: to_address_hash, - from_address_hash: from_address_hash - }) do - [to_string(to_address_hash), to_string(from_address_hash)] - end - - defp item_to_address_hash_strings(%Log{address_hash: address_hash}) do - [to_string(address_hash)] - end - - defp item_to_address_hash_strings(%Withdrawal{address_hash: address_hash}) do - [to_string(address_hash)] - end - - defp item_to_address_hash_strings(%Block{miner_hash: miner_hash}) do - [to_string(miner_hash)] - end - - defp item_to_address_hash_strings(%CurrentTokenBalance{address_hash: address_hash}) do - [to_string(address_hash)] - end - - defp item_to_address_hash_strings({%Address{} = address, _}) do - item_to_address_hash_strings(address) - end - - defp item_to_address_hash_strings(%Address{hash: hash}) do - [to_string(hash)] - end - - defp put_ens_names(names, items) do - Enum.map(items, &put_ens_name_to_item(&1, names)) - end - - defp put_ens_name_to_item( - %Transaction{ - to_address_hash: to_address_hash, - created_contract_address_hash: created_contract_address_hash, - from_address_hash: from_address_hash - } = tx, - names - ) do - token_transfers = - case tx.token_transfers do - token_transfers_list when is_list(token_transfers_list) -> - Enum.map(token_transfers_list, &put_ens_name_to_item(&1, names)) - - other -> - other - end - - %Transaction{ - tx - | to_address: alter_address(tx.to_address, to_address_hash, names), - created_contract_address: alter_address(tx.created_contract_address, created_contract_address_hash, names), - from_address: alter_address(tx.from_address, from_address_hash, names), - token_transfers: token_transfers - } - end - - defp put_ens_name_to_item( - %TokenTransfer{ - to_address_hash: to_address_hash, - from_address_hash: from_address_hash - } = tt, - names - ) do - %TokenTransfer{ - tt - | to_address: alter_address(tt.to_address, to_address_hash, names), - from_address: alter_address(tt.from_address, from_address_hash, names) - } - end - - defp put_ens_name_to_item( - %InternalTransaction{ - to_address_hash: to_address_hash, - created_contract_address_hash: created_contract_address_hash, - from_address_hash: from_address_hash - } = tx, - names - ) do - %InternalTransaction{ - tx - | to_address: alter_address(tx.to_address, to_address_hash, names), - created_contract_address: alter_address(tx.created_contract_address, created_contract_address_hash, names), - from_address: alter_address(tx.from_address, from_address_hash, names) - } - end - - defp put_ens_name_to_item(%Log{address_hash: address_hash} = log, names) do - %Log{log | address: alter_address(log.address, address_hash, names)} - end - - defp put_ens_name_to_item(%Withdrawal{address_hash: address_hash} = withdrawal, names) do - %Withdrawal{withdrawal | address: alter_address(withdrawal.address, address_hash, names)} - end - - defp put_ens_name_to_item(%Block{miner_hash: miner_hash} = block, names) do - %Block{block | miner: alter_address(block.miner, miner_hash, names)} - end - - defp put_ens_name_to_item(%CurrentTokenBalance{address_hash: address_hash} = current_token_balance, names) do - %CurrentTokenBalance{ - current_token_balance - | address: alter_address(current_token_balance.address, address_hash, names) - } - end - - defp put_ens_name_to_item({%Address{} = address, count}, names) do - {put_ens_name_to_item(address, names), count} - end - - defp put_ens_name_to_item(%Address{} = address, names) do - alter_address(address, address.hash, names) + @doc """ + Preloads ENS data to the list if BENS is enabled + """ + @spec maybe_preload_ens(MetadataPreloader.supported_input()) :: MetadataPreloader.supported_input() + def maybe_preload_ens(argument) do + maybe_preload_meta(argument, __MODULE__, &MetadataPreloader.preload_ens_to_list/1) end - defp alter_address(_, nil, _names) do - nil + @doc """ + Preloads ENS data to the list of the search results if BENS is enabled + """ + @spec maybe_preload_ens_info_to_search_results(list()) :: list() + def maybe_preload_ens_info_to_search_results(list) do + maybe_preload_meta(list, __MODULE__, &MetadataPreloader.preload_ens_info_to_search_results/1) end - defp alter_address(%NotLoaded{}, address_hash, names) do - %{ens_domain_name: names[to_string(address_hash)]} + @doc """ + Preloads ENS data to the transaction results if BENS is enabled + """ + @spec maybe_preload_ens_to_transaction(Transaction.t()) :: Transaction.t() + def maybe_preload_ens_to_transaction(transaction) do + maybe_preload_meta(transaction, __MODULE__, &MetadataPreloader.preload_ens_to_transaction/1) end - defp alter_address(%Address{} = address, address_hash, names) do - %Address{address | ens_domain_name: names[to_string(address_hash)]} + @doc """ + Preloads ENS data to the address results if BENS is enabled + """ + @spec maybe_preload_ens_to_address(Address.t()) :: Address.t() + def maybe_preload_ens_to_address(address) do + maybe_preload_meta(address, __MODULE__, &MetadataPreloader.preload_ens_to_address/1) end end diff --git a/apps/explorer/lib/explorer/microservice_interfaces/metadata.ex b/apps/explorer/lib/explorer/microservice_interfaces/metadata.ex new file mode 100644 index 0000000000..93274376cd --- /dev/null +++ b/apps/explorer/lib/explorer/microservice_interfaces/metadata.ex @@ -0,0 +1,93 @@ +defmodule Explorer.MicroserviceInterfaces.Metadata do + @moduledoc """ + Module to interact with Metadata microservice + """ + + alias Explorer.Chain.{Address.MetadataPreloader, Transaction} + alias Explorer.Utility.Microservice + alias HTTPoison.Response + + import Explorer.Chain.Address.MetadataPreloader, only: [maybe_preload_meta: 3] + + require Logger + @post_timeout :timer.seconds(5) + + @tags_per_address_limit 5 + @request_error_msg "Error while sending request to Metadata microservice" + + @spec get_addresses_tags([String.t()]) :: {:error, :disabled | <<_::416>> | Jason.DecodeError.t()} | {:ok, any()} + def get_addresses_tags(addresses) do + with :ok <- Microservice.check_enabled(__MODULE__) do + body = %{ + addresses: addresses, + tags: %{ + limit: to_string(@tags_per_address_limit) + } + } + + http_post_request(addresses_metadata_url(), body) + end + end + + defp http_post_request(url, body) do + headers = [{"Content-Type", "application/json"}] + + case HTTPoison.post(url, Jason.encode!(body), headers, recv_timeout: @post_timeout) do + {:ok, %Response{body: body, status_code: 200}} -> + body |> Jason.decode() |> decode_meta() + + {_, error} -> + Logger.error(fn -> + [ + "Error while sending request to Metadata microservice url: #{url}, body: #{inspect(body)}: ", + inspect(error) + ] + end) + + {:error, @request_error_msg} + end + end + + defp addresses_metadata_url do + "#{base_url()}/metadata" + end + + defp base_url do + "#{Microservice.base_url(__MODULE__)}/api/v1" + end + + @spec enabled?() :: boolean() + def enabled?, do: Microservice.check_enabled(__MODULE__) == :ok + + @doc """ + Preloads metadata to supported entities if Metadata microservice is enabled + """ + @spec maybe_preload_metadata(MetadataPreloader.supported_input()) :: MetadataPreloader.supported_input() + def maybe_preload_metadata(argument) do + maybe_preload_meta(argument, __MODULE__, &MetadataPreloader.preload_metadata_to_list/1) + end + + @doc """ + Preloads metadata to transaction if Metadata microservice is enabled + """ + @spec maybe_preload_metadata_to_transaction(Transaction.t()) :: Transaction.t() + def maybe_preload_metadata_to_transaction(transaction) do + maybe_preload_meta(transaction, __MODULE__, &MetadataPreloader.preload_metadata_to_transaction/1) + end + + defp decode_meta({:ok, %{"addresses" => addresses} = result}) do + prepared_address = + Enum.reduce(addresses, %{}, fn {address, meta}, acc -> + prepared_meta = Map.put(meta, "tags", meta["tags"] |> Enum.map(&decode_meta_in_tag/1)) + Map.put(acc, address, prepared_meta) + end) + + {:ok, Map.put(result, "addresses", prepared_address)} + end + + defp decode_meta(other), do: other + + defp decode_meta_in_tag(%{"meta" => meta} = tag) do + Map.put(tag, "meta", Jason.decode!(meta)) + end +end diff --git a/config/runtime.exs b/config/runtime.exs index 56d12677eb..0e11ced48f 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -473,6 +473,10 @@ config :explorer, Explorer.MicroserviceInterfaces.AccountAbstraction, service_url: System.get_env("MICROSERVICE_ACCOUNT_ABSTRACTION_URL"), enabled: ConfigHelper.parse_bool_env_var("MICROSERVICE_ACCOUNT_ABSTRACTION_ENABLED") +config :explorer, Explorer.MicroserviceInterfaces.Metadata, + service_url: System.get_env("MICROSERVICE_METADATA_URL"), + enabled: ConfigHelper.parse_bool_env_var("MICROSERVICE_METADATA_ENABLED") + config :explorer, :air_table_public_tags, table_url: System.get_env("ACCOUNT_PUBLIC_TAGS_AIRTABLE_URL"), api_key: System.get_env("ACCOUNT_PUBLIC_TAGS_AIRTABLE_API_KEY") diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index 4f1af4df7e..e4dee6d526 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -318,6 +318,8 @@ MICROSERVICE_SIG_PROVIDER_URL=http://sig-provider:8050/ # MICROSERVICE_BENS_ENABLED= #MICROSERVICE_ACCOUNT_ABSTRACTION_ENABLED=true #MICROSERVICE_ACCOUNT_ABSTRACTION_URL= +# MICROSERVICE_METADATA_URL= +# MICROSERVICE_METADATA_ENABLED= DECODE_NOT_A_CONTRACT_CALLS=true # DATABASE_READ_ONLY_API_URL= # ACCOUNT_DATABASE_URL=