feat: Integrate Metadata microservice (#9706)
* feat: Integrate Metadata microservice * Fix formattingpull/9812/head
parent
36df683929
commit
45fa2aab5a
@ -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 |
@ -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 |
Loading…
Reference in new issue