feat: blobs in search

pull/9168/head
Kirill Fedoseev 10 months ago
parent f26c92ebf9
commit 1bc0e50245
  1. 54
      apps/block_scout_web/lib/block_scout_web/chain.ex
  2. 2
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/blob_controller.ex
  3. 2
      apps/block_scout_web/lib/block_scout_web/templates/search/_tile.html.eex
  4. 16
      apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex
  5. 24
      apps/explorer/lib/explorer/chain/beacon/reader.ex
  6. 51
      apps/explorer/lib/explorer/chain/search.ex

@ -15,8 +15,6 @@ defmodule BlockScoutWeb.Chain do
token_contract_address_from_token_name: 1 token_contract_address_from_token_name: 1
] ]
alias Explorer.Chain.UserOperation
import Explorer.Helper, only: [parse_integer: 1] import Explorer.Helper, only: [parse_integer: 1]
alias Ecto.Association.NotLoaded alias Ecto.Association.NotLoaded
@ -27,6 +25,8 @@ defmodule BlockScoutWeb.Chain do
Address, Address,
Address.CoinBalance, Address.CoinBalance,
Address.CurrentTokenBalance, Address.CurrentTokenBalance,
Beacon,
Beacon.Blob,
Block, Block,
Hash, Hash,
InternalTransaction, InternalTransaction,
@ -37,6 +37,7 @@ defmodule BlockScoutWeb.Chain do
TokenTransfer, TokenTransfer,
Transaction, Transaction,
Transaction.StateChange, Transaction.StateChange,
UserOperation,
Wei, Wei,
Withdrawal Withdrawal
} }
@ -56,7 +57,7 @@ defmodule BlockScoutWeb.Chain do
@page_size 50 @page_size 50
@default_paging_options %PagingOptions{page_size: @page_size + 1} @default_paging_options %PagingOptions{page_size: @page_size + 1}
@address_hash_len 40 @address_hash_len 40
@tx_block_op_hash_len 64 @full_hash_len 64
def default_paging_options do def default_paging_options do
@default_paging_options @default_paging_options
@ -83,20 +84,20 @@ defmodule BlockScoutWeb.Chain do
end end
@spec from_param(String.t()) :: @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(param)
def from_param("0x" <> number_string = param) when byte_size(number_string) == @address_hash_len, def from_param("0x" <> number_string = param) when byte_size(number_string) == @address_hash_len,
do: address_from_param(param) do: address_from_param(param)
def from_param("0x" <> number_string = param) when byte_size(number_string) == @tx_block_op_hash_len, def from_param("0x" <> number_string = param) when byte_size(number_string) == @full_hash_len,
do: block_or_transaction_or_operation_from_param(param) do: block_or_transaction_or_operation_or_blob_from_param(param)
def from_param(param) when byte_size(param) == @address_hash_len, def from_param(param) when byte_size(param) == @address_hash_len,
do: address_from_param("0x" <> param) do: address_from_param("0x" <> param)
def from_param(param) when byte_size(param) == @tx_block_op_hash_len, def from_param(param) when byte_size(param) == @full_hash_len,
do: block_or_transaction_or_operation_from_param("0x" <> param) do: block_or_transaction_or_operation_or_blob_from_param("0x" <> param)
def from_param(string) when is_binary(string) do def from_param(string) when is_binary(string) do
case param_to_block_number(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} %{"fiat_value" => ctb.fiat_value, "value" => value, "id" => id}
end end
defp block_or_transaction_or_operation_from_param(param) do defp block_or_transaction_or_operation_or_blob_from_param(param) do
with {:error, :not_found} <- transaction_from_param(param) do with {:ok, hash} <- string_to_transaction_hash(param),
hash_string_to_block_or_operation(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
end end
defp transaction_from_param(param) do defp hash_to_user_operation(hash) do
case string_to_transaction_hash(param) do if UserOperation.user_operations_enabled?() do
{:ok, hash} -> UserOperation.hash_to_user_operation(hash)
hash_to_transaction(hash) else
{:error, :not_found}
:error ->
{:error, :not_found}
end end
end end
defp hash_string_to_block_or_operation(hash_string) do defp hash_to_blob(hash) do
with {:ok, hash} <- string_to_block_hash(hash_string), if Application.get_env(:explorer, :chain_type) == "ethereum" do
{:error, :not_found} <- hash_to_block(hash), Beacon.Reader.blob(hash, false)
{:user_operations_enabled, true} <- {:user_operations_enabled, UserOperation.user_operations_enabled?()} do
UserOperation.hash_to_user_operation(hash)
else else
{:user_operations_enabled, false} -> {:error, :not_found} {:error, :not_found}
:error -> {:error, :not_found}
res -> res
end end
end end

@ -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 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) 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} -> {:ok, blob} ->
conn conn
|> put_status(200) |> put_status(200)

@ -82,6 +82,8 @@
transaction_hash: "0x" <> Base.encode16(@result.tx_hash, case: :lower) %> transaction_hash: "0x" <> Base.encode16(@result.tx_hash, case: :lower) %>
<% "user_operation" -> %> <% "user_operation" -> %>
<%= "0x" <> Base.encode16(@result.user_operation_hash, case: :lower) %> <%= "0x" <> Base.encode16(@result.user_operation_hash, case: :lower) %>
<% "blob" -> %>
<%= "0x" <> Base.encode16(@result.blob_hash, case: :lower) %>
<% "block" -> %> <% "block" -> %>
<%= link( <%= link(
"0x" <> Base.encode16(@result.block_hash, case: :lower), "0x" <> Base.encode16(@result.block_hash, case: :lower),

@ -3,7 +3,7 @@ defmodule BlockScoutWeb.API.V2.SearchView do
alias BlockScoutWeb.{BlockView, Endpoint} alias BlockScoutWeb.{BlockView, Endpoint}
alias Explorer.Chain 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 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} %{"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 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{bytes: bytes}), do: hash_to_string(bytes)
defp hash_to_string(hash), do: "0x" <> Base.encode16(hash, case: :lower) 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 defp redirect_search_results(%UserOperation{} = item) do
%{"type" => "user_operation", "parameter" => to_string(item.hash)} %{"type" => "user_operation", "parameter" => to_string(item.hash)}
end end
defp redirect_search_results(%Blob{} = item) do
%{"type" => "blob", "parameter" => to_string(item.hash)}
end
end end

@ -11,7 +11,8 @@ defmodule Explorer.Chain.Beacon.Reader do
where: 2, where: 2,
where: 3, where: 3,
join: 5, join: 5,
select: 3 select: 3,
select_merge: 3
] ]
import Explorer.Chain, only: [select_repo: 1] 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 Returns `{:ok, %Explorer.Chain.Beacon.Blob{}}` if found
iex> %Explorer.Chain.Beacon.Blob{hash: hash} = insert(:blob) 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 iex> found_hash == hash
true true
@ -35,14 +36,23 @@ defmodule Explorer.Chain.Beacon.Reader do
iex> {:ok, hash} = Explorer.Chain.string_to_transaction_hash( iex> {:ok, hash} = Explorer.Chain.string_to_transaction_hash(
...> "0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b" ...> "0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b"
...> ) ...> )
iex> Explorer.Chain.Beacon.Reader.blob(hash) iex> Explorer.Chain.Beacon.Reader.blob(hash, true)
{:error, :not_found} {:error, :not_found}
""" """
@spec blob(Hash.Full.t(), [Chain.api?()]) :: {:error, :not_found} | {:ok, Blob.t()} @spec blob(Hash.Full.t(), boolean(), [Chain.api?()]) :: {:error, :not_found} | {:ok, Blob.t()}
def blob(hash, options \\ []) when is_list(options) do def blob(hash, with_data, options \\ []) when is_list(options) do
Blob query =
|> where(hash: ^hash) if with_data do
Blob
|> where(hash: ^hash)
else
Blob
|> where(hash: ^hash)
|> select_merge([_], %{blob_data: nil})
end
query
|> select_repo(options).one() |> select_repo(options).one()
|> case do |> case do
nil -> {:error, :not_found} nil -> {:error, :not_found}

@ -20,6 +20,7 @@ defmodule Explorer.Chain.Search do
alias Explorer.Chain.{ alias Explorer.Chain.{
Address, Address,
Beacon.Blob,
Block, Block,
DenormalizationHelper, DenormalizationHelper,
SmartContract, SmartContract,
@ -101,17 +102,28 @@ defmodule Explorer.Chain.Search do
valid_full_hash?(string) -> valid_full_hash?(string) ->
tx_query = search_tx_query(string) tx_query = search_tx_query(string)
if UserOperation.user_operations_enabled?() do tx_block_query =
user_operation_query = search_user_operation_query(string)
basic_query basic_query
|> union(^tx_query) |> union(^tx_query)
|> union(^user_operation_query)
|> union(^block_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 else
basic_query tx_block_op_query
|> union(^tx_query)
|> union(^block_query)
end end
block_query -> block_query ->
@ -137,6 +149,7 @@ defmodule Explorer.Chain.Search do
2. Results couldn't be paginated 2. Results couldn't be paginated
""" """
@spec balanced_unpaginated_search(PagingOptions.t(), binary(), [Chain.api?()] | []) :: list @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 def balanced_unpaginated_search(paging_options, raw_search_query, options \\ []) do
search_query = String.trim(raw_search_query) search_query = String.trim(raw_search_query)
ens_task = Task.async(fn -> search_ens_name(raw_search_query, options) end) ens_task = Task.async(fn -> search_ens_name(raw_search_query, options) end)
@ -189,6 +202,15 @@ defmodule Explorer.Chain.Search do
[] []
end 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 = address_result =
if query = search_address_query(search_query) do if query = search_address_query(search_query) do
query query
@ -215,6 +237,7 @@ defmodule Explorer.Chain.Search do
labels_result, labels_result,
tx_result, tx_result,
op_result, op_result,
blob_result,
address_result, address_result,
blocks_result, blocks_result,
ens_result ens_result
@ -431,6 +454,19 @@ defmodule Explorer.Chain.Search do
) )
end 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 defp search_block_query(term) do
block_search_fields = block_search_fields =
search_fields() search_fields()
@ -586,6 +622,7 @@ defmodule Explorer.Chain.Search do
address_hash: dynamic([_], type(^nil, :binary)), address_hash: dynamic([_], type(^nil, :binary)),
tx_hash: dynamic([_], type(^nil, :binary)), tx_hash: dynamic([_], type(^nil, :binary)),
user_operation_hash: dynamic([_], type(^nil, :binary)), user_operation_hash: dynamic([_], type(^nil, :binary)),
blob_hash: dynamic([_], type(^nil, :binary)),
block_hash: dynamic([_], type(^nil, :binary)), block_hash: dynamic([_], type(^nil, :binary)),
type: nil, type: nil,
name: nil, name: nil,

Loading…
Cancel
Save