Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
71 changes: 71 additions & 0 deletions apps/explorer/lib/explorer/genesis.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
defmodule Explorer.Genesis do
@moduledoc """
Helpers for importing genesis data into the Explorer database.

Acts as a thin wrapper around the existing chain spec importers so callers (CLI tasks,
scripts, etc.) can load a spec, optionally dry-run validation, and invoke the correct
importer without needing to know the underlying modules.
"""

alias Explorer.ChainSpec.{Geth, Parity}

@type format :: :geth | :parity | String.t()
@type import_result :: Explorer.Chain.Import.all_result()

@importers %{
geth: Geth.Importer,
parity: Parity.Importer
}

@doc """
Imports the provided chain specification.

## Options

* `:dry_run` - when true, no data is written; the function returns counts of the
derived params so callers can verify the spec ahead of time.
"""
@spec import(any(), format(), keyword()) :: import_result() | {:ok, map()} | {:error, term()}
def import(spec, format \\ :geth, opts \\ [])

def import(spec, format, opts) do
importer = fetch_importer!(format)
dry_run? = Keyword.get(opts, :dry_run, false)

if dry_run? do
dry_run(importer, spec)
else
importer.import_genesis_accounts(spec)
end
end

defp dry_run(importer, spec) do
accounts = importer.genesis_accounts(spec)

{:ok,
%{
addresses: length(accounts),
has_contract_code?: Enum.any?(accounts, &Map.get(&1, :contract_code)),
has_nonce?: Enum.any?(accounts, &Map.get(&1, :nonce))
}}
rescue
error -> {:error, error}
end

defp fetch_importer!(format) when is_binary(format) do
format
|> String.downcase()
|> String.to_existing_atom()
|> fetch_importer!()
rescue
ArgumentError ->
raise ArgumentError, "Unsupported genesis format: #{format}"
end

defp fetch_importer!(format) when is_atom(format) do
Map.fetch!(@importers, format)
rescue
KeyError ->
raise ArgumentError, "Unsupported genesis format: #{format}"
end
end
78 changes: 78 additions & 0 deletions apps/explorer/lib/mix/tasks/genesis_import.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
defmodule Mix.Tasks.Explorer.GenesisImport do
@moduledoc """
CLI helper for seeding the database with genesis data.

mix explorer.genesis_import --spec path/to/genesis.json --format geth

Pass `--dry-run` to validate the spec without touching the database.
"""
@shortdoc "Imports genesis data (addresses/balances) into Explorer"

use Mix.Task

alias Explorer.Genesis

@switches [spec: :string, format: :string, dry_run: :boolean]
@aliases [s: :spec, f: :format]

@impl Mix.Task
def run(args) do
Mix.Task.run("app.start")

{opts, _rest, invalid} = OptionParser.parse(args, strict: @switches, aliases: @aliases)

if invalid != [] do
Mix.raise("Invalid options: #{inspect(invalid)}")
end

spec_path =
opts[:spec] ||
Mix.raise("""
Missing --spec option.

Example:
mix explorer.genesis_import --spec config/genesis.json --format geth
""")

format = opts[:format] || "geth"
dry_run? = opts[:dry_run] || false

spec_map = load_spec(spec_path)

Mix.shell().info(
"Importing genesis spec from #{spec_label(spec_path)} (format=#{format}, dry_run=#{dry_run?})"
)

case Genesis.import(spec_map, format, dry_run: dry_run?) do
{:ok, summary} ->
Mix.shell().info("Genesis import finished: #{inspect(summary)}")

{:error, reason} ->
Mix.raise("Genesis import failed: #{inspect(reason)}")
end
end

defp load_spec("-") do
IO.read(:stdio, :all)
|> decode_spec!("<stdin>")
end

defp load_spec(path) do
path
|> File.read!()
|> decode_spec!(path)
end

defp decode_spec!(raw, label) do
case Jason.decode(raw) do
{:ok, spec} ->
spec

{:error, reason} ->
Mix.raise("Could not parse genesis spec from #{label}: #{inspect(reason)}")
end
end

defp spec_label("-"), do: "<stdin>"
defp spec_label(path), do: path
end
30 changes: 30 additions & 0 deletions apps/explorer/test/explorer/genesis_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
defmodule Explorer.GenesisTest do
use ExUnit.Case, async: true

alias Explorer.Genesis

describe "import/3 dry-run" do
test "summarizes geth specs" do
spec = %{
"alloc" => %{
"0x0000000000000000000000000000000000000001" => %{"balance" => "0x1"},
"0x0000000000000000000000000000000000000002" => %{
"balance" => "0x2",
"bytecode" => "0x6000"
}
}
}

assert {:ok, summary} = Genesis.import(spec, :geth, dry_run: true)
assert summary.addresses == 2
assert summary.has_contract_code?
refute summary.has_nonce?
end

test "fails fast on unknown format" do
assert_raise ArgumentError, fn ->
Genesis.import(%{}, :unknown, dry_run: true)
end
end
end
end
14 changes: 7 additions & 7 deletions docker/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,19 @@ STABLE_TAG := $(RELEASE_VERSION)

start:
@echo "==> Starting blockscout db"
@docker-compose -f ../docker-compose/services/db.yml up -d
@docker compose -f ../docker-compose/services/db.yml up -d
@echo "==> Starting blockscout backend"
@docker-compose -f ../docker-compose/services/backend.yml up -d
@docker compose -f ../docker-compose/services/backend.yml up -d
@echo "==> Starting stats microservice"
@docker-compose -f ../docker-compose/services/stats.yml up -d
@docker compose -f ../docker-compose/services/stats.yml up -d
@echo "==> Starting visualizer microservice"
@docker-compose -f ../docker-compose/services/visualizer.yml up -d
@docker compose -f ../docker-compose/services/visualizer.yml up -d
@echo "==> Starting sig-provider microservice"
@docker-compose -f ../docker-compose/services/sig-provider.yml up -d
@docker compose -f ../docker-compose/services/sig-provider.yml up -d
@echo "==> Starting blockscout frontend"
@docker-compose -f ../docker-compose/services/frontend.yml up -d
@docker compose -f ../docker-compose/services/frontend.yml up -d
@echo "==> Starting Nginx proxy"
@docker-compose -f ../docker-compose/services/nginx.yml up -d
@docker compose -f ../docker-compose/services/nginx.yml up -d

BS_BACKEND_STARTED := $(shell docker ps --no-trunc --filter name=^/${BACKEND_CONTAINER_NAME}$ | grep ${BACKEND_CONTAINER_NAME})
BS_FRONTEND_STARTED := $(shell docker ps --no-trunc --filter name=^/${FRONTEND_CONTAINER_NAME}$ | grep ${FRONTEND_CONTAINER_NAME})
Expand Down
74 changes: 74 additions & 0 deletions genesis.json

Large diffs are not rendered by default.