From f6230c54ece38f6d30f5723f5a36701b29378793 Mon Sep 17 00:00:00 2001 From: Radek Bochenek Date: Sun, 27 Jul 2025 23:38:34 +0200 Subject: [PATCH 01/18] feat(wip): Entity search API --- .../templates/search/_tile.html.eex | 2 ++ .../views/api/v2/search_view.ex | 13 +++++++- .../lib/explorer/chain/golem_base/entity.ex | 32 +++++++++++++++++++ apps/explorer/lib/explorer/chain/search.ex | 29 +++++++++++++++-- 4 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 apps/explorer/lib/explorer/chain/golem_base/entity.ex diff --git a/apps/block_scout_web/lib/block_scout_web/templates/search/_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/search/_tile.html.eex index 981d1dd803..77eb8551a5 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/search/_tile.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/search/_tile.html.eex @@ -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" -> %> diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex index d0efb14ddc..0f112de2a2 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex @@ -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 @@ -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, @@ -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 diff --git a/apps/explorer/lib/explorer/chain/golem_base/entity.ex b/apps/explorer/lib/explorer/chain/golem_base/entity.ex new file mode 100644 index 0000000000..e534a3a0b1 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/golem_base/entity.ex @@ -0,0 +1,32 @@ +defmodule Explorer.Chain.GolemBase.Entity do + @moduledoc """ + The representation of a Golem Base entity + """ + + require Logger + + import Ecto.Query + + use Explorer.Schema + 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) + + timestamps() + end + + def changeset(%__MODULE__{} = golembase_entity, attrs) do + golembase_entity + |> cast(attrs, [:key]) + |> validate_required([:key]) + end + + def enabled? do + # TODO: Add environment variable to enable/disable Golem Base Indexer + true + end +end diff --git a/apps/explorer/lib/explorer/chain/search.ex b/apps/explorer/lib/explorer/chain/search.ex index b5f3cd2065..0900f8c832 100644 --- a/apps/explorer/lib/explorer/chain/search.ex +++ b/apps/explorer/lib/explorer/chain/search.ex @@ -22,6 +22,7 @@ defmodule Explorer.Chain.Search do Beacon.Blob, Block, DenormalizationHelper, + GolemBase, Hash, SmartContract, Token, @@ -191,14 +192,24 @@ defmodule Explorer.Chain.Search do |> search_transaction_query() |> union_all(^search_block_by_hash_query(full_hash)) + transaction_block_golembase_entity_query = + if GolemBase.Entity.enabled?() do + 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 = @@ -688,6 +699,19 @@ defmodule Explorer.Chain.Search do ) 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, + select: ^golembase_entity_search_fields + ) + end + defp search_blob_query(term) do blob_search_fields = search_fields() @@ -1020,6 +1044,7 @@ defmodule Explorer.Chain.Search do 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, From d6fdd27c025e8e29df72d8e5eca247cafcbc73b0 Mon Sep 17 00:00:00 2001 From: Radek Bochenek Date: Mon, 28 Jul 2025 22:02:05 +0200 Subject: [PATCH 02/18] feat: add env var GOLEMBASE_ENABLED --- .env-dev | 2 ++ apps/explorer/lib/explorer/chain/golem_base/entity.ex | 5 +---- config/runtime.exs | 3 +++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.env-dev b/.env-dev index 0579bcc34d..6bf3bfc006 100644 --- a/.env-dev +++ b/.env-dev @@ -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' diff --git a/apps/explorer/lib/explorer/chain/golem_base/entity.ex b/apps/explorer/lib/explorer/chain/golem_base/entity.ex index e534a3a0b1..032bf04408 100644 --- a/apps/explorer/lib/explorer/chain/golem_base/entity.ex +++ b/apps/explorer/lib/explorer/chain/golem_base/entity.ex @@ -3,8 +3,6 @@ defmodule Explorer.Chain.GolemBase.Entity do The representation of a Golem Base entity """ - require Logger - import Ecto.Query use Explorer.Schema @@ -26,7 +24,6 @@ defmodule Explorer.Chain.GolemBase.Entity do end def enabled? do - # TODO: Add environment variable to enable/disable Golem Base Indexer - true + Application.get_env(:explorer, __MODULE__)[:enabled] end end diff --git a/config/runtime.exs b/config/runtime.exs index 609bf4cb2b..a3257857dc 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -844,6 +844,9 @@ config :explorer, Explorer.Chain.Filecoin.NativeAddress, config :explorer, Explorer.Chain.Blackfort.Validator, api_url: System.get_env("BLACKFORT_VALIDATOR_API_URL") +config :explorer, Explorer.Chain.GolemBase.Entity, + enabled: ConfigHelper.parse_bool_env_var("GOLEMBASE_ENABLED", "false") + addresses_blacklist_url = ConfigHelper.parse_microservice_url("ADDRESSES_BLACKLIST_URL") config :explorer, Explorer.Chain.Fetcher.AddressesBlacklist, From cf7d166ca9cac274a9a2fd0a6de1a29f17a131e6 Mon Sep 17 00:00:00 2001 From: Radek Bochenek Date: Mon, 28 Jul 2025 22:03:29 +0200 Subject: [PATCH 03/18] feat(wip): add migration to create golem_base_entities --- .../20250728191950_create_golem_base_entities.exs | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 apps/explorer/priv/repo/migrations/20250728191950_create_golem_base_entities.exs diff --git a/apps/explorer/priv/repo/migrations/20250728191950_create_golem_base_entities.exs b/apps/explorer/priv/repo/migrations/20250728191950_create_golem_base_entities.exs new file mode 100644 index 0000000000..3674bd9ab0 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20250728191950_create_golem_base_entities.exs @@ -0,0 +1,9 @@ +defmodule Explorer.Repo.Migrations.CreateGolemBaseEntities do + use Ecto.Migration + + def change do + create table(:golem_base_entities, primary_key: false) do + add(:key, :bytea, null: false, primary_key: true) + end + end +end From 97e0049e98af1ff458aaba7991cb9bfbfffd275e Mon Sep 17 00:00:00 2001 From: Radek Bochenek Date: Mon, 28 Jul 2025 22:43:05 +0200 Subject: [PATCH 04/18] feat: add migration to create golem_base_entities --- ...50728191950_create_golem_base_entities.exs | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/apps/explorer/priv/repo/migrations/20250728191950_create_golem_base_entities.exs b/apps/explorer/priv/repo/migrations/20250728191950_create_golem_base_entities.exs index 3674bd9ab0..e7e71f0772 100644 --- a/apps/explorer/priv/repo/migrations/20250728191950_create_golem_base_entities.exs +++ b/apps/explorer/priv/repo/migrations/20250728191950_create_golem_base_entities.exs @@ -1,9 +1,41 @@ defmodule Explorer.Repo.Migrations.CreateGolemBaseEntities do use Ecto.Migration - def change do + 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, :bytea, null: false, primary_key: true) + 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 From 291e99e175e177afa3b15e7af127be02211c6430 Mon Sep 17 00:00:00 2001 From: Radek Bochenek Date: Mon, 28 Jul 2025 23:05:39 +0200 Subject: [PATCH 05/18] feat: add owner --- apps/explorer/lib/explorer/chain/golem_base/entity.ex | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/golem_base/entity.ex b/apps/explorer/lib/explorer/chain/golem_base/entity.ex index 032bf04408..6a653e1725 100644 --- a/apps/explorer/lib/explorer/chain/golem_base/entity.ex +++ b/apps/explorer/lib/explorer/chain/golem_base/entity.ex @@ -8,19 +8,18 @@ defmodule Explorer.Chain.GolemBase.Entity do use Explorer.Schema 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(:owner, :binary, null: false) timestamps() end def changeset(%__MODULE__{} = golembase_entity, attrs) do golembase_entity - |> cast(attrs, [:key]) - |> validate_required([:key]) + |> cast(attrs, [:key, :owner]) + |> validate_required([:key, :owner]) end def enabled? do From 7f2ef97185c02c1fbb02e23edbee81ba2093f59a Mon Sep 17 00:00:00 2001 From: Radek Bochenek Date: Mon, 28 Jul 2025 23:19:55 +0200 Subject: [PATCH 06/18] feat: add status --- apps/explorer/lib/explorer/chain/golem_base/entity.ex | 5 +++-- apps/explorer/lib/explorer/chain/search.ex | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/golem_base/entity.ex b/apps/explorer/lib/explorer/chain/golem_base/entity.ex index 6a653e1725..3c0cfb65ac 100644 --- a/apps/explorer/lib/explorer/chain/golem_base/entity.ex +++ b/apps/explorer/lib/explorer/chain/golem_base/entity.ex @@ -11,6 +11,7 @@ defmodule Explorer.Chain.GolemBase.Entity do @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) timestamps() @@ -18,8 +19,8 @@ defmodule Explorer.Chain.GolemBase.Entity do def changeset(%__MODULE__{} = golembase_entity, attrs) do golembase_entity - |> cast(attrs, [:key, :owner]) - |> validate_required([:key, :owner]) + |> cast(attrs, [:key, :status, :owner]) + |> validate_required([:key, :status, :owner]) end def enabled? do diff --git a/apps/explorer/lib/explorer/chain/search.ex b/apps/explorer/lib/explorer/chain/search.ex index 0900f8c832..3aca157612 100644 --- a/apps/explorer/lib/explorer/chain/search.ex +++ b/apps/explorer/lib/explorer/chain/search.ex @@ -708,6 +708,7 @@ defmodule Explorer.Chain.Search do 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 From b432970ddb6409d79a634f2f3566e254a92f6dd8 Mon Sep 17 00:00:00 2001 From: Radek Bochenek Date: Tue, 29 Jul 2025 00:22:05 +0200 Subject: [PATCH 07/18] feat: check-redirect support --- .../lib/block_scout_web/chain.ex | 13 ++++++++++- .../lib/explorer/chain/golem_base/entity.ex | 22 ++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/chain.ex b/apps/block_scout_web/lib/block_scout_web/chain.ex index f9b14893e3..c598cfe713 100644 --- a/apps/block_scout_web/lib/block_scout_web/chain.ex +++ b/apps/block_scout_web/lib/block_scout_web/chain.ex @@ -33,6 +33,7 @@ defmodule BlockScoutWeb.Chain do Beacon.Blob, Block, Block.Reward, + GolemBase, Hash, InternalTransaction, Log, @@ -90,7 +91,8 @@ defmodule BlockScoutWeb.Chain do 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, @@ -929,6 +931,7 @@ defmodule BlockScoutWeb.Chain do {: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 @@ -945,6 +948,14 @@ defmodule BlockScoutWeb.Chain do end end + defp hash_to_golembase_entity(hash) do + if GolemBase.Entity.enabled?() do + GolemBase.Entity.hash_to_golembase_entity(hash) + 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) diff --git a/apps/explorer/lib/explorer/chain/golem_base/entity.ex b/apps/explorer/lib/explorer/chain/golem_base/entity.ex index 3c0cfb65ac..908b8045f6 100644 --- a/apps/explorer/lib/explorer/chain/golem_base/entity.ex +++ b/apps/explorer/lib/explorer/chain/golem_base/entity.ex @@ -3,11 +3,14 @@ defmodule Explorer.Chain.GolemBase.Entity do The representation of a Golem Base entity """ - import Ecto.Query + 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) @@ -23,6 +26,23 @@ defmodule Explorer.Chain.GolemBase.Entity do |> validate_required([:key, :status, :owner]) 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 From f5a3bb92da883963a3d0ad5671c706555d1fee91 Mon Sep 17 00:00:00 2001 From: Radek Bochenek Date: Tue, 29 Jul 2025 01:20:21 +0200 Subject: [PATCH 08/18] feat: tests --- .../api/v2/search_controller_test.exs | 85 +++++++++++++++++++ .../lib/explorer/chain/golem_base/entity.ex | 6 +- apps/explorer/test/support/factory.ex | 31 +++++++ 3 files changed, 120 insertions(+), 2 deletions(-) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/search_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/search_controller_test.exs index 2e759956a2..6a0bc4d032 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/search_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/search_controller_test.exs @@ -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) @@ -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) @@ -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" diff --git a/apps/explorer/lib/explorer/chain/golem_base/entity.ex b/apps/explorer/lib/explorer/chain/golem_base/entity.ex index 908b8045f6..1a28fcfa2c 100644 --- a/apps/explorer/lib/explorer/chain/golem_base/entity.ex +++ b/apps/explorer/lib/explorer/chain/golem_base/entity.ex @@ -16,14 +16,16 @@ defmodule Explorer.Chain.GolemBase.Entity 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]) - |> validate_required([:key, :status, :owner]) + |> 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?]) :: diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index d7d03ac0c6..9a0581d23c 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -35,6 +35,7 @@ defmodule Explorer.Factory do Block, ContractMethod, Data, + GolemBase, Hash, InternalTransaction, Log, @@ -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 From e5c794fbec0ff3388bf3da4140c4b748959d63eb Mon Sep 17 00:00:00 2001 From: Radek Bochenek Date: Tue, 29 Jul 2025 09:25:46 +0200 Subject: [PATCH 09/18] fix: add golembase --- cspell.json | 1 + 1 file changed, 1 insertion(+) diff --git a/cspell.json b/cspell.json index 0ab5bc6af3..b4da197b47 100644 --- a/cspell.json +++ b/cspell.json @@ -284,6 +284,7 @@ "giga", "Gitter", "goldtoken", + "golembase", "goqtclhifepvfnicv", "gqz", "granitegrey", From 1108ff0d1d80e21f5f70951721ff98dc3eca22cd Mon Sep 17 00:00:00 2001 From: Radek Bochenek Date: Tue, 29 Jul 2025 10:12:32 +0200 Subject: [PATCH 10/18] chore: alias nested module to fix credo --- apps/block_scout_web/lib/block_scout_web/chain.ex | 6 +++--- apps/explorer/lib/explorer/chain/search.ex | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/chain.ex b/apps/block_scout_web/lib/block_scout_web/chain.ex index c598cfe713..e5201bd668 100644 --- a/apps/block_scout_web/lib/block_scout_web/chain.ex +++ b/apps/block_scout_web/lib/block_scout_web/chain.ex @@ -33,7 +33,7 @@ defmodule BlockScoutWeb.Chain do Beacon.Blob, Block, Block.Reward, - GolemBase, + GolemBase.Entity, Hash, InternalTransaction, Log, @@ -949,8 +949,8 @@ defmodule BlockScoutWeb.Chain do end defp hash_to_golembase_entity(hash) do - if GolemBase.Entity.enabled?() do - GolemBase.Entity.hash_to_golembase_entity(hash) + if Entity.enabled?() do + Entity.hash_to_golembase_entity(hash) else {:error, :not_found} end diff --git a/apps/explorer/lib/explorer/chain/search.ex b/apps/explorer/lib/explorer/chain/search.ex index 3aca157612..d675edd3ab 100644 --- a/apps/explorer/lib/explorer/chain/search.ex +++ b/apps/explorer/lib/explorer/chain/search.ex @@ -22,7 +22,7 @@ defmodule Explorer.Chain.Search do Beacon.Blob, Block, DenormalizationHelper, - GolemBase, + GolemBase.Entity, Hash, SmartContract, Token, @@ -193,7 +193,7 @@ defmodule Explorer.Chain.Search do |> union_all(^search_block_by_hash_query(full_hash)) transaction_block_golembase_entity_query = - if GolemBase.Entity.enabled?() do + if Entity.enabled?() do golembase_entity_query = search_golembase_entity_query(full_hash) transaction_block_query @@ -705,7 +705,7 @@ defmodule Explorer.Chain.Search do |> Map.put(:golembase_entity, dynamic([golembase_entity: golembase_entity], golembase_entity.key)) |> Map.put(:type, "golembase_entity") - from(golembase_entity in GolemBase.Entity, + from(golembase_entity in Entity, as: :golembase_entity, where: golembase_entity.key == ^term, where: golembase_entity.status == :active, From 4f09d75b97fce38dd1ed48cfebf422d2f70187a7 Mon Sep 17 00:00:00 2001 From: Radek Bochenek Date: Tue, 29 Jul 2025 12:23:12 +0200 Subject: [PATCH 11/18] feat: run golem_base_entities migration in test environment only --- ...50728191950_create_golem_base_entities.exs | 58 ++++++++++--------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/apps/explorer/priv/repo/migrations/20250728191950_create_golem_base_entities.exs b/apps/explorer/priv/repo/migrations/20250728191950_create_golem_base_entities.exs index e7e71f0772..8a4e808222 100644 --- a/apps/explorer/priv/repo/migrations/20250728191950_create_golem_base_entities.exs +++ b/apps/explorer/priv/repo/migrations/20250728191950_create_golem_base_entities.exs @@ -2,40 +2,44 @@ 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$$; - """) + if Mix.env() == :test 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) + 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(: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()")) + add(:inserted_at, :naive_datetime, null: false, default: fragment("now()")) + add(:updated_at, :naive_datetime, null: false, default: fragment("now()")) + end end end def down do - drop(table(:golem_base_entities)) + if Mix.env() == :test 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$$ - """) + # 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 end From 48a04ca2f88f4af57b550b79ef1e6550bc7372f9 Mon Sep 17 00:00:00 2001 From: Radek Bochenek Date: Tue, 29 Jul 2025 15:07:37 +0200 Subject: [PATCH 12/18] fix: avoid Mix.env() --- .../20250728191950_create_golem_base_entities.exs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/explorer/priv/repo/migrations/20250728191950_create_golem_base_entities.exs b/apps/explorer/priv/repo/migrations/20250728191950_create_golem_base_entities.exs index 8a4e808222..2f2a7aacdf 100644 --- a/apps/explorer/priv/repo/migrations/20250728191950_create_golem_base_entities.exs +++ b/apps/explorer/priv/repo/migrations/20250728191950_create_golem_base_entities.exs @@ -1,8 +1,10 @@ defmodule Explorer.Repo.Migrations.CreateGolemBaseEntities do use Ecto.Migration + @env Mix.env + def up do - if Mix.env() == :test do + if @env == :test do # Create golem_base_entity_status_type enum type if it doesn't exist execute(""" DO $$ @@ -30,7 +32,7 @@ defmodule Explorer.Repo.Migrations.CreateGolemBaseEntities do end def down do - if Mix.env() == :test do + if @env == :test do drop(table(:golem_base_entities)) # Drop golem_base_entity_status_type enum if it exists From eddaae41cf75fe6af61f03b1112e2d44619ec2e4 Mon Sep 17 00:00:00 2001 From: Radek Bochenek Date: Tue, 29 Jul 2025 15:42:07 +0200 Subject: [PATCH 13/18] fix: replace Mix.env() with custom config --- apps/explorer/config/dev.exs | 2 ++ apps/explorer/config/prod.exs | 2 ++ apps/explorer/config/test.exs | 2 ++ .../20250728191950_create_golem_base_entities.exs | 6 ++---- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/explorer/config/dev.exs b/apps/explorer/config/dev.exs index b6a0f56ad2..ee7333294d 100644 --- a/apps/explorer/config/dev.exs +++ b/apps/explorer/config/dev.exs @@ -51,3 +51,5 @@ config :logger, :token_instances, level: :debug, path: Path.absname("logs/dev/explorer/tokens/token_instances.log"), metadata_filter: [fetcher: :token_instances] + +config :explorer, :environment, :dev diff --git a/apps/explorer/config/prod.exs b/apps/explorer/config/prod.exs index b272b96fb9..84c52e420f 100644 --- a/apps/explorer/config/prod.exs +++ b/apps/explorer/config/prod.exs @@ -59,3 +59,5 @@ config :logger, :token_instances, path: Path.absname("logs/prod/explorer/tokens/token_instances.log"), metadata_filter: [fetcher: :token_instances], rotate: %{max_bytes: 52_428_800, keep: 19} + +config :explorer, :environment, :prod diff --git a/apps/explorer/config/test.exs b/apps/explorer/config/test.exs index 675668e45d..61ef17a13f 100644 --- a/apps/explorer/config/test.exs +++ b/apps/explorer/config/test.exs @@ -105,3 +105,5 @@ config :explorer, Explorer.Chain.Fetcher.CheckBytecodeMatchingOnDemand, enabled: config :explorer, Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand, enabled: false config :tesla, adapter: Explorer.Mock.TeslaAdapter + +config :explorer, :environment, :test diff --git a/apps/explorer/priv/repo/migrations/20250728191950_create_golem_base_entities.exs b/apps/explorer/priv/repo/migrations/20250728191950_create_golem_base_entities.exs index 2f2a7aacdf..5db1dd3da7 100644 --- a/apps/explorer/priv/repo/migrations/20250728191950_create_golem_base_entities.exs +++ b/apps/explorer/priv/repo/migrations/20250728191950_create_golem_base_entities.exs @@ -1,10 +1,8 @@ defmodule Explorer.Repo.Migrations.CreateGolemBaseEntities do use Ecto.Migration - @env Mix.env - def up do - if @env == :test do + if Application.get_env(:explorer, :environment) == :test do # Create golem_base_entity_status_type enum type if it doesn't exist execute(""" DO $$ @@ -32,7 +30,7 @@ defmodule Explorer.Repo.Migrations.CreateGolemBaseEntities do end def down do - if @env == :test do + if Application.get_env(:explorer, :environment) == :test do drop(table(:golem_base_entities)) # Drop golem_base_entity_status_type enum if it exists From 86a51267b3fde588688431d12fd981d8114fec2d Mon Sep 17 00:00:00 2001 From: Radek Bochenek Date: Tue, 29 Jul 2025 16:23:23 +0200 Subject: [PATCH 14/18] fix: skip tests when GolemBase support is disabled --- .../api/v2/search_controller_test.exs | 96 +++++++++++-------- 1 file changed, 57 insertions(+), 39 deletions(-) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/search_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/search_controller_test.exs index 6a0bc4d032..9b559b4709 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/search_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/search_controller_test.exs @@ -1,44 +1,50 @@ defmodule BlockScoutWeb.API.V2.SearchControllerTest do use BlockScoutWeb.ConnCase - alias Explorer.Chain.{Address, Block} + alias Explorer.Chain.{Address, Block, GolemBase.Entity} alias Explorer.Tags.AddressTag alias Plug.Conn.Query describe "/search" do test "search golembase entity", %{conn: conn} do - golembase_entity = insert(:golembase_entity_active) + if Entity.enabled?() do + golembase_entity = insert(:golembase_entity_active) - request = get(conn, "/api/v2/search?q=#{golembase_entity.key}") - assert response = json_response(request, 200) + 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 + assert Enum.count(response["items"]) == 1 + assert response["next_page_params"] == nil - item = Enum.at(response["items"], 0) + item = Enum.at(response["items"], 0) - assert item["type"] == "golembase_entity" - assert item["golembase_entity"] == to_string(golembase_entity.key) + assert item["type"] == "golembase_entity" + assert item["golembase_entity"] == to_string(golembase_entity.key) + end end test "search golembase entity with status deleted", %{conn: conn} do - golembase_entity = insert(:golembase_entity_deleted) + if Entity.enabled?() do + golembase_entity = insert(:golembase_entity_deleted) - request = get(conn, "/api/v2/search?q=#{golembase_entity.key}") - assert response = json_response(request, 200) + 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 + assert Enum.count(response["items"]) == 0 + assert response["next_page_params"] == nil + end end test "search golembase entity with status expired", %{conn: conn} do - golembase_entity = insert(:golembase_entity_expired) + if Entity.enabled?() do + golembase_entity = insert(:golembase_entity_expired) - request = get(conn, "/api/v2/search?q=#{golembase_entity.key}") - assert response = json_response(request, 200) + 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 + assert Enum.count(response["items"]) == 0 + assert response["next_page_params"] == nil + end end test "search block", %{conn: conn} do @@ -1760,28 +1766,34 @@ defmodule BlockScoutWeb.API.V2.SearchControllerTest do describe "/search/check-redirect" do test "search golembase entity", %{conn: conn} do - golembase_entity = insert(:golembase_entity_active) - hash = to_string(golembase_entity.key) + if Entity.enabled?() 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}") + request = get(conn, "/api/v2/search/check-redirect?q=#{golembase_entity.key}") - assert %{"redirect" => true, "type" => "golembase_entity", "parameter" => ^hash} = json_response(request, 200) + assert %{"redirect" => true, "type" => "golembase_entity", "parameter" => ^hash} = json_response(request, 200) + end end test "search golembase entity with status deleted", %{conn: conn} do - golembase_entity = insert(:golembase_entity_deleted) + if Entity.enabled?() do + golembase_entity = insert(:golembase_entity_deleted) - request = get(conn, "/api/v2/search/check-redirect?q=#{golembase_entity.key}") + request = get(conn, "/api/v2/search/check-redirect?q=#{golembase_entity.key}") - assert %{"redirect" => false, "type" => null, "parameter" => null} = json_response(request, 200) + assert %{"redirect" => false, "type" => null, "parameter" => null} = json_response(request, 200) + end end test "search golembase entity with status expired", %{conn: conn} do - golembase_entity = insert(:golembase_entity_expired) + if Entity.enabled?() do + golembase_entity = insert(:golembase_entity_expired) - request = get(conn, "/api/v2/search/check-redirect?q=#{golembase_entity.key}") + request = get(conn, "/api/v2/search/check-redirect?q=#{golembase_entity.key}") - assert %{"redirect" => false, "type" => null, "parameter" => null} = json_response(request, 200) + assert %{"redirect" => false, "type" => null, "parameter" => null} = json_response(request, 200) + end end test "finds a consensus block by block number", %{conn: conn} do @@ -1883,28 +1895,34 @@ defmodule BlockScoutWeb.API.V2.SearchControllerTest do describe "/search/quick" do test "search golembase entity", %{conn: conn} do - golembase_entity = insert(:golembase_entity_active) - hash = to_string(golembase_entity.key) + if Entity.enabled?() do + golembase_entity = insert(:golembase_entity_active) + hash = to_string(golembase_entity.key) - request = get(conn, "/api/v2/search/quick?q=#{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) + assert [%{"golembase_entity" => ^hash, "type" => "golembase_entity"}] = json_response(request, 200) + end end test "search golembase entity with status deleted", %{conn: conn} do - golembase_entity = insert(:golembase_entity_deleted) + if Entity.enabled?() do + golembase_entity = insert(:golembase_entity_deleted) - request = get(conn, "/api/v2/search/quick?q=#{golembase_entity.key}") + request = get(conn, "/api/v2/search/quick?q=#{golembase_entity.key}") - assert [] = json_response(request, 200) + assert [] = json_response(request, 200) + end end test "search golembase entity with status expired", %{conn: conn} do - golembase_entity = insert(:golembase_entity_expired) + if Entity.enabled?() do + golembase_entity = insert(:golembase_entity_expired) - request = get(conn, "/api/v2/search/quick?q=#{golembase_entity.key}") + request = get(conn, "/api/v2/search/quick?q=#{golembase_entity.key}") - assert [] = json_response(request, 200) + assert [] = json_response(request, 200) + end end test "check that all categories are in response list", %{conn: conn} do From 59ed1c32795896ad891a32ea4b3b5c90cea2110a Mon Sep 17 00:00:00 2001 From: Radek Bochenek Date: Tue, 29 Jul 2025 16:33:48 +0200 Subject: [PATCH 15/18] feat: enable Golem Base support for tests --- .github/workflows/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index fd26e53e91..aa6b41d91b 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -784,6 +784,7 @@ jobs: mix compile mix test --no-start --exclude no_nethermind env: + GOLEMBASE_ENABLED: "true" # match POSTGRES_PASSWORD for postgres image below PGPASSWORD: postgres # match POSTGRES_USER for postgres image below From 0770f2ba398cb74c481a1e5d9f4cce080b4ede65 Mon Sep 17 00:00:00 2001 From: Radek Bochenek Date: Tue, 29 Jul 2025 16:46:50 +0200 Subject: [PATCH 16/18] fix: dialyzer flag --halt-exit-status is no longer valid --- .github/workflows/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index aa6b41d91b..998751b19d 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -256,7 +256,7 @@ jobs: CHAIN_TYPE: ${{ matrix.chain-type != 'default' && matrix.chain-type || '' }} - name: Run Dialyzer - run: mix dialyzer --halt-exit-status + run: mix dialyzer env: CHAIN_TYPE: ${{ matrix.chain-type != 'default' && matrix.chain-type || '' }} From d2f2a65d489442f01d31661eb096daedf3de86f7 Mon Sep 17 00:00:00 2001 From: Radek Bochenek Date: Tue, 29 Jul 2025 17:51:43 +0200 Subject: [PATCH 17/18] chore: update :pattern_match entries for lib/explorer/chain/search.ex --- .dialyzer_ignore.exs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.dialyzer_ignore.exs b/.dialyzer_ignore.exs index 233b0967a9..0818cf7fbe 100644 --- a/.dialyzer_ignore.exs +++ b/.dialyzer_ignore.exs @@ -10,7 +10,7 @@ {"lib/explorer/smart_contract/stylus/publisher_worker.ex", :exact_eq, 14}, {"lib/explorer/smart_contract/stylus/publisher_worker.ex", :pattern_match, 14}, ~r/lib\/phoenix\/router.ex/, - {"lib/explorer/chain/search.ex", :pattern_match, 100}, - {"lib/explorer/chain/search.ex", :pattern_match, 283}, - {"lib/explorer/chain/search.ex", :pattern_match, 383} + {"lib/explorer/chain/search.ex", :pattern_match, 101}, + {"lib/explorer/chain/search.ex", :pattern_match, 294}, + {"lib/explorer/chain/search.ex", :pattern_match, 394} ] From 6f2fdeb9636b8f6a40ce8b4e11502c1d9e8f708b Mon Sep 17 00:00:00 2001 From: Radek Bochenek Date: Tue, 29 Jul 2025 17:55:06 +0200 Subject: [PATCH 18/18] fix: dialyzer flag --halt-exit-status is not longer valid --- .circleci/config.yml | 2 +- apps/block_scout_web/README.md | 2 +- apps/explorer/README.md | 2 +- bin/test | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 03275c1f98..f3ef80acc9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -224,7 +224,7 @@ jobs: paths: - plts - - run: mix dialyzer --halt-exit-status + - run: mix dialyzer eslint: docker: # Ensure .tool-versions matches diff --git a/apps/block_scout_web/README.md b/apps/block_scout_web/README.md index 448152b981..22e81b4804 100644 --- a/apps/block_scout_web/README.md +++ b/apps/block_scout_web/README.md @@ -31,7 +31,7 @@ You can also run IEx (Interactive Elixir): `$ iex -S mix phx.server` (This can b * Build the assets: `cd assets && npm run build` * Format the Elixir code: `mix format` * Lint the Elixir code: `mix credo --strict` -* Run the dialyzer: `mix dialyzer --halt-exit-status` +* Run the dialyzer: `mix dialyzer` * Check the Elixir code for vulnerabilities: `mix sobelow --config` * Update translation templates and translations and check there are no uncommitted changes: `mix gettext.extract --merge` * Lint the JavaScript code: `cd assets && npm run eslint` diff --git a/apps/explorer/README.md b/apps/explorer/README.md index c283c1c521..5e53d3b17c 100644 --- a/apps/explorer/README.md +++ b/apps/explorer/README.md @@ -26,7 +26,7 @@ To get BlockScout up and running locally: * Format the Elixir code: `$ mix format` * Lint the Elixir code: `$ mix credo --strict` -* Run the dialyzer: `mix dialyzer --halt-exit-status` +* Run the dialyzer: `mix dialyzer` * Check the Elixir code for vulnerabilities: `$ mix sobelow --config` ### Benchmarking diff --git a/bin/test b/bin/test index 2d4a432bd4..1074871656 100755 --- a/bin/test +++ b/bin/test @@ -5,5 +5,5 @@ set -ex mix format --check-formatted mix credo --strict mix sobelow --config -mix dialyzer --halt-exit-status +mix dialyzer mix test