feat: blobs in search

pull/9168/head
Kirill Fedoseev 10 months ago
parent f26c92ebf9
commit 1bc0e50245
  1. 52
      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. 20
      apps/explorer/lib/explorer/chain/beacon/reader.ex
  6. 49
      apps/explorer/lib/explorer/chain/search.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 ->
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

@ -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)

@ -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),

@ -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

@ -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
@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}

@ -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)
tx_block_query =
basic_query
|> union(^tx_query)
|> union(^block_query)
tx_block_op_query =
if UserOperation.user_operations_enabled?() do
user_operation_query = search_user_operation_query(string)
basic_query
|> union(^tx_query)
tx_block_query
|> union(^user_operation_query)
|> union(^block_query)
else
basic_query
|> union(^tx_query)
|> union(^block_query)
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
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,

Loading…
Cancel
Save