Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env-dev
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export GOLEMBASE_ENABLED=true

export SECRET_BASE_KEY='REPLACE_WITH_GENERATED_SECRET_BASE_KEY'
export DATABASE_URL='postgresql://blockscout:l3explorer@localhost:5432/blockscout'

Expand Down
13 changes: 12 additions & 1 deletion apps/block_scout_web/lib/block_scout_web/chain.ex
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
Beacon.Blob,
Block,
Block.Reward,
GolemBase,
Hash,
InternalTransaction,
Log,
Expand Down Expand Up @@ -90,7 +91,8 @@
end

@spec from_param(String.t()) ::
{:ok, Address.t() | Block.t() | Transaction.t() | UserOperation.t() | Blob.t()} | {:error, :not_found}
{:ok, Address.t() | Block.t() | Transaction.t() | UserOperation.t() | GolemBase.Entity | Blob.t()}
| {:error, :not_found}
def from_param(param)

def from_param("0x" <> number_string = param) when byte_size(number_string) == @address_hash_len,
Expand Down Expand Up @@ -929,6 +931,7 @@
{: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_golembase_entity(hash),
{:error, :not_found} <- hash_to_blob(hash) do
{:error, :not_found}
else
Expand All @@ -945,6 +948,14 @@
end
end

defp hash_to_golembase_entity(hash) do
if GolemBase.Entity.enabled?() do

Check warning on line 952 in apps/block_scout_web/lib/block_scout_web/chain.ex

View workflow job for this annotation

GitHub Actions / Credo

Nested modules could be aliased at the top of the invoking module.
GolemBase.Entity.hash_to_golembase_entity(hash)

Check warning on line 953 in apps/block_scout_web/lib/block_scout_web/chain.ex

View workflow job for this annotation

GitHub Actions / Credo

Nested modules could be aliased at the top of the invoking module.
else
{:error, :not_found}
end
end

defp hash_to_blob(hash) do
if Application.get_env(:explorer, :chain_type) == :ethereum do
BeaconReader.blob(hash, false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@
transaction_hash: "0x" <> Base.encode16(@result.transaction_hash, case: :lower) %>
<% "user_operation" -> %>
<%= "0x" <> Base.encode16(@result.user_operation_hash, case: :lower) %>
<% "golembase_entity" -> %>
<%= "0x" <> Base.encode16(@result.golembase_entity, case: :lower) %>
<% "blob" -> %>
<%= "0x" <> Base.encode16(@result.blob_hash, case: :lower) %>
<% "block" -> %>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule BlockScoutWeb.API.V2.SearchView do

alias BlockScoutWeb.{BlockView, Endpoint}
alias Explorer.Chain
alias Explorer.Chain.{Address, Beacon.Blob, Block, Hash, Transaction, UserOperation}
alias Explorer.Chain.{Address, Beacon.Blob, Block, GolemBase, Hash, Transaction, UserOperation}
alias Plug.Conn.Query

def render("search_results.json", %{search_results: search_results, next_page_params: next_page_params}) do
Expand Down Expand Up @@ -137,6 +137,13 @@ defmodule BlockScoutWeb.API.V2.SearchView do
}
end

def prepare_search_result(%{type: "golembase_entity"} = search_result) do
%{
"type" => search_result.type,
"golembase_entity" => hash_to_string(search_result.golembase_entity)
}
end

def prepare_search_result(%{type: "blob"} = search_result) do
%{
"type" => search_result.type,
Expand Down Expand Up @@ -189,6 +196,10 @@ defmodule BlockScoutWeb.API.V2.SearchView do
%{"type" => "user_operation", "parameter" => to_string(item.hash)}
end

defp redirect_search_results(%GolemBase.Entity{} = item) do
%{"type" => "golembase_entity", "parameter" => to_string(item.key)}
end

defp redirect_search_results(%Blob{} = item) do
%{"type" => "blob", "parameter" => to_string(item.hash)}
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,41 @@ defmodule BlockScoutWeb.API.V2.SearchControllerTest do
alias Plug.Conn.Query

describe "/search" do
test "search golembase entity", %{conn: conn} do
golembase_entity = insert(:golembase_entity_active)

request = get(conn, "/api/v2/search?q=#{golembase_entity.key}")
assert response = json_response(request, 200)

assert Enum.count(response["items"]) == 1
assert response["next_page_params"] == nil

item = Enum.at(response["items"], 0)

assert item["type"] == "golembase_entity"
assert item["golembase_entity"] == to_string(golembase_entity.key)
end

test "search golembase entity with status deleted", %{conn: conn} do
golembase_entity = insert(:golembase_entity_deleted)

request = get(conn, "/api/v2/search?q=#{golembase_entity.key}")
assert response = json_response(request, 200)

assert Enum.count(response["items"]) == 0
assert response["next_page_params"] == nil
end

test "search golembase entity with status expired", %{conn: conn} do
golembase_entity = insert(:golembase_entity_expired)

request = get(conn, "/api/v2/search?q=#{golembase_entity.key}")
assert response = json_response(request, 200)

assert Enum.count(response["items"]) == 0
assert response["next_page_params"] == nil
end

test "search block", %{conn: conn} do
block = insert(:block)

Expand Down Expand Up @@ -1724,6 +1759,31 @@ defmodule BlockScoutWeb.API.V2.SearchControllerTest do
end

describe "/search/check-redirect" do
test "search golembase entity", %{conn: conn} do
golembase_entity = insert(:golembase_entity_active)
hash = to_string(golembase_entity.key)

request = get(conn, "/api/v2/search/check-redirect?q=#{golembase_entity.key}")

assert %{"redirect" => true, "type" => "golembase_entity", "parameter" => ^hash} = json_response(request, 200)
end

test "search golembase entity with status deleted", %{conn: conn} do
golembase_entity = insert(:golembase_entity_deleted)

request = get(conn, "/api/v2/search/check-redirect?q=#{golembase_entity.key}")

assert %{"redirect" => false, "type" => null, "parameter" => null} = json_response(request, 200)
end

test "search golembase entity with status expired", %{conn: conn} do
golembase_entity = insert(:golembase_entity_expired)

request = get(conn, "/api/v2/search/check-redirect?q=#{golembase_entity.key}")

assert %{"redirect" => false, "type" => null, "parameter" => null} = json_response(request, 200)
end

test "finds a consensus block by block number", %{conn: conn} do
block = insert(:block)

Expand Down Expand Up @@ -1822,6 +1882,31 @@ defmodule BlockScoutWeb.API.V2.SearchControllerTest do
end

describe "/search/quick" do
test "search golembase entity", %{conn: conn} do
golembase_entity = insert(:golembase_entity_active)
hash = to_string(golembase_entity.key)

request = get(conn, "/api/v2/search/quick?q=#{golembase_entity.key}")

assert [%{"golembase_entity" => ^hash, "type" => "golembase_entity"}] = json_response(request, 200)
end

test "search golembase entity with status deleted", %{conn: conn} do
golembase_entity = insert(:golembase_entity_deleted)

request = get(conn, "/api/v2/search/quick?q=#{golembase_entity.key}")

assert [] = json_response(request, 200)
end

test "search golembase entity with status expired", %{conn: conn} do
golembase_entity = insert(:golembase_entity_expired)

request = get(conn, "/api/v2/search/quick?q=#{golembase_entity.key}")

assert [] = json_response(request, 200)
end

test "check that all categories are in response list", %{conn: conn} do
name = "156000"

Expand Down
51 changes: 51 additions & 0 deletions apps/explorer/lib/explorer/chain/golem_base/entity.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
defmodule Explorer.Chain.GolemBase.Entity do
@moduledoc """
The representation of a Golem Base entity
"""

import Ecto.Query, only: [where: 2]

use Explorer.Schema
alias Explorer.Chain
alias Explorer.Chain.Hash

@type api? :: {:api?, true | false}

@primary_key false
typed_schema "golem_base_entities" do
field(:key, Hash.Full, primary_key: true, null: false)
field(:status, Ecto.Enum, values: [:active, :deleted, :expired])
field(:owner, :binary, null: false)
field(:last_updated_at_tx_hash, :binary, null: false)
field(:expires_at_block_number, :integer, null: false)

timestamps()
end

def changeset(%__MODULE__{} = golembase_entity, attrs) do
golembase_entity
|> cast(attrs, [:key, :status, :owner, :last_updated_at_tx_hash, :expires_at_block_number])
|> validate_required([:key, :status, :owner, :last_updated_at_tx_hash, :expires_at_block_number])
end

@spec hash_to_golembase_entity(Hash.Full.t(), [api?]) ::
{:ok, __MODULE__.t()} | {:error, :not_found}
def hash_to_golembase_entity(%Hash{byte_count: unquote(Hash.Full.byte_count())} = hash, options \\ [])
when is_list(options) do
__MODULE__
|> where(key: ^hash)
|> where(status: :active)
|> Chain.select_repo(options).one()
|> case do
nil ->
{:error, :not_found}

golembase_entity ->
{:ok, golembase_entity}
end
end

def enabled? do
Application.get_env(:explorer, __MODULE__)[:enabled]
end
end
30 changes: 28 additions & 2 deletions apps/explorer/lib/explorer/chain/search.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
Beacon.Blob,
Block,
DenormalizationHelper,
GolemBase,
Hash,
SmartContract,
Token,
Expand Down Expand Up @@ -191,14 +192,24 @@
|> search_transaction_query()
|> union_all(^search_block_by_hash_query(full_hash))

transaction_block_golembase_entity_query =
if GolemBase.Entity.enabled?() do

Check warning on line 196 in apps/explorer/lib/explorer/chain/search.ex

View workflow job for this annotation

GitHub Actions / Credo

Nested modules could be aliased at the top of the invoking module.
golembase_entity_query = search_golembase_entity_query(full_hash)

transaction_block_query
|> union_all(^golembase_entity_query)
else
transaction_block_query
end

transaction_block_op_query =
if UserOperation.enabled?() do
user_operation_query = search_user_operation_query(full_hash)

transaction_block_query
transaction_block_golembase_entity_query
|> union_all(^user_operation_query)
else
transaction_block_query
transaction_block_golembase_entity_query
end

result_query =
Expand Down Expand Up @@ -688,6 +699,20 @@
)
end

defp search_golembase_entity_query(term) do
golembase_entity_search_fields =
search_fields()
|> Map.put(:golembase_entity, dynamic([golembase_entity: golembase_entity], golembase_entity.key))
|> Map.put(:type, "golembase_entity")

from(golembase_entity in GolemBase.Entity,
as: :golembase_entity,
where: golembase_entity.key == ^term,
where: golembase_entity.status == :active,
select: ^golembase_entity_search_fields
)
end

defp search_blob_query(term) do
blob_search_fields =
search_fields()
Expand Down Expand Up @@ -1020,6 +1045,7 @@
user_operation_hash: dynamic(type(^nil, :binary)),
blob_hash: dynamic(type(^nil, :binary)),
block_hash: dynamic(type(^nil, :binary)),
golembase_entity: dynamic(type(^nil, :binary)),
type: nil,
name: nil,
symbol: nil,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
defmodule Explorer.Repo.Migrations.CreateGolemBaseEntities do
use Ecto.Migration

def up do
# Create golem_base_entity_status_type enum type if it doesn't exist
execute("""
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'golem_base_entity_status_type') THEN
CREATE TYPE golem_base_entity_status_type AS ENUM ('active', 'deleted', 'expired');
END IF;
END$$;
""")

create table(:golem_base_entities, primary_key: false) do
add(:key, :binary, null: false, primary_key: true)
add(:data, :binary)
add(:status, :golem_base_entity_status_type, null: false)
add(:owner, :binary, null: false)

add(:created_at_tx_hash, :binary)
add(:last_updated_at_tx_hash, :binary, null: false)
add(:expires_at_block_number, :bigint, null: false)

add(:inserted_at, :naive_datetime, null: false, default: fragment("now()"))
add(:updated_at, :naive_datetime, null: false, default: fragment("now()"))
end
end

def down do
drop(table(:golem_base_entities))

# Drop golem_base_entity_status_type enum if it exists
execute("""
DO $$
BEGIN
DROP TYPE IF EXISTS golem_base_entity_status_type;
END$$
""")
end
end
31 changes: 31 additions & 0 deletions apps/explorer/test/support/factory.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ defmodule Explorer.Factory do
Block,
ContractMethod,
Data,
GolemBase,
Hash,
InternalTransaction,
Log,
Expand Down Expand Up @@ -1406,4 +1407,34 @@ defmodule Explorer.Factory do
inserted_at: DateTime.utc_now()
}
end

def golembase_entity_active_factory do
%GolemBase.Entity{
key: "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
owner: "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
status: "active",
last_updated_at_tx_hash: "0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
expires_at_block_number: 999
}
end

def golembase_entity_deleted_factory do
%GolemBase.Entity{
key: "0x9999999999999999999999999999999999999999999999999999999999999999",
owner: "0x88888888888888888888888888888888",
status: "deleted",
last_updated_at_tx_hash: "0x7777777777777777777777777777777777777777777777777777777777777777",
expires_at_block_number: 100_000_000
}
end

def golembase_entity_expired_factory do
%GolemBase.Entity{
key: "0x6666666666666666666666666666666666666666666666666666666666666666",
owner: "0x55555555555555555555555555555555",
status: "expired",
last_updated_at_tx_hash: "0x4444444444444444444444444444444444444444444444444444444444444444",
expires_at_block_number: 1
}
end
end
Loading
Loading