Land #331: Extract Explorer.Indexer to Indexer

pull/347/head
Luke Imhoff 7 years ago committed by GitHub
commit 2066427b29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      README.md
  2. 6
      apps/ethereum_jsonrpc/mix.exs
  3. 3
      apps/ethereum_jsonrpc/test/test_helper.exs
  4. 4
      apps/explorer/config/config.exs
  5. 1
      apps/explorer/lib/explorer/application.ex
  6. 26
      apps/explorer/lib/explorer/indexer/supervisor.ex
  7. 4
      apps/explorer/mix.exs
  8. 3
      apps/explorer/test/test_helper.exs
  9. 8
      apps/explorer_web/mix.exs
  10. 7
      apps/explorer_web/test/test_helper.exs
  11. 4
      apps/indexer/.formatter.exs
  12. 24
      apps/indexer/.gitignore
  13. 21
      apps/indexer/README.md
  14. 9
      apps/indexer/config/config.exs
  15. 22
      apps/indexer/lib/indexer.ex
  16. 8
      apps/indexer/lib/indexer/address_balance_fetcher.ex
  17. 14
      apps/indexer/lib/indexer/address_extraction.ex
  18. 24
      apps/indexer/lib/indexer/application.ex
  19. 14
      apps/indexer/lib/indexer/block_fetcher.ex
  20. 4
      apps/indexer/lib/indexer/buffered_task.ex
  21. 8
      apps/indexer/lib/indexer/internal_transaction_fetcher.ex
  22. 10
      apps/indexer/lib/indexer/pending_transaction_fetcher.ex
  23. 2
      apps/indexer/lib/indexer/sequence.ex
  24. 49
      apps/indexer/mix.exs
  25. 6
      apps/indexer/test/indexer/address_balance_fetcher_test.exs
  26. 16
      apps/indexer/test/indexer/address_extraction_test.exs
  27. 16
      apps/indexer/test/indexer/block_fetcher_test.exs
  28. 4
      apps/indexer/test/indexer/buffered_task_test.exs
  29. 12
      apps/indexer/test/indexer/internal_transaction_fetcher_test.exs
  30. 6
      apps/indexer/test/indexer/pending_transaction_fetcher_test.exs
  31. 4
      apps/indexer/test/indexer/sequence_test.exs
  32. 4
      apps/indexer/test/indexer_test.exs
  33. 4
      apps/indexer/test/support/indexer/address_balance_fetcher_case.ex
  34. 4
      apps/indexer/test/support/indexer/internal_transaction_fetcher_case.ex
  35. 20
      apps/indexer/test/test_helper.exs
  36. 2
      coveralls.json
  37. 12
      mix.exs

@ -33,6 +33,19 @@ Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
You can also run IEx (Interactive Elixir): `iex -S mix phx.server` You can also run IEx (Interactive Elixir): `iex -S mix phx.server`
#### Umbrella Project Organization
This repository is an [umbrella project](https://elixir-lang.org/getting-started/mix-otp/dependencies-and-umbrella-projects.html): each directory under `apps/` is a separate [Mix](https://hexdocs.pm/mix/Mix.html) project and [OTP application](https://hexdocs.pm/elixir/Application.html), but the projects can use each other as a dependency in their `mix.exs`.
Each OTP application has a restricted domain
| Directory | OTP Application | Namespace | Purpose |
|:------------------------|:--------------------|:------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `apps/ethereum_jsonrpc` | `:ethereum_jsonrpc` | `EthereumJSONRPC` | Ethereum JSONRPC client. It is allowed to know `Explorer`'s param format, but it cannot directly depend on `:explorer` |
| `apps/explorer` | `:explorer` | `Explorer` | Storage for the indexed chain. Can read and write to the backing storage. MUST be able to boot in a read-only mode when run independently from `:indexer`, so cannot depend on `:indexer` as that would start `:indexer` indexing. |
| `apps/explorer_web` | `:explorer_web` | `ExplorerWeb` | Phoenix interface to `:explorer`. The minimum interface to allow web access should go in `:explorer_web`. Any business rules or interface that is not tied directly to `Phoenix` or `Plug` should go in `:explorer`. MUST be able to boot in a read-only mode when run independently from `:indexer`, so cannot depend on `:indexer` as that would start `:indexer` indexing. |
| `apps/indexer` | `:indexer` | `Indexer` | Uses `:ethereum_jsonrpc` to index chain and batch import data into `:explorer`. Any process, `Task`, or `GenServer` that automatically reads from the chain and writes to `:explorer` should be in `:indexer`, so that automatic writes are restricted to `:indexer` and read-only mode can be achieved by not running `:indexer`. |
### CircleCI Updates ### CircleCI Updates
Configure your local CCMenu with the following url: [`https://circleci.com/gh/poanetwork/poa-explorer.cc.xml?circle-token=f8823a3d0090407c11f87028c73015a331dbf604`](https://circleci.com/gh/poanetwork/poa-explorer.cc.xml?circle-token=f8823a3d0090407c11f87028c73015a331dbf604) Configure your local CCMenu with the following url: [`https://circleci.com/gh/poanetwork/poa-explorer.cc.xml?circle-token=f8823a3d0090407c11f87028c73015a331dbf604`](https://circleci.com/gh/poanetwork/poa-explorer.cc.xml?circle-token=f8823a3d0090407c11f87028c73015a331dbf604)

@ -38,7 +38,11 @@ defmodule EthereumJsonrpc.MixProject do
end end
defp aliases(env) do defp aliases(env) do
env_aliases(env) [
# to match behavior of `mix test` from project root, which needs to not start applications for `indexer` to
# prevent its supervision tree from starting, which is undesirable in test
test: "test --no-start"
] ++ env_aliases(env)
end end
defp env_aliases(:dev), do: [] defp env_aliases(:dev), do: []

@ -3,5 +3,8 @@ junit_folder = Mix.Project.build_path() <> "/junit/#{Mix.Project.config()[:app]}
File.mkdir_p!(junit_folder) File.mkdir_p!(junit_folder)
:ok = Application.put_env(:junit_formatter, :report_dir, junit_folder) :ok = Application.put_env(:junit_formatter, :report_dir, junit_folder)
# Counter `test --no-start`. `--no-start` is needed for `:indexer` compatibility
{:ok, _} = Application.ensure_all_started(:ethereum_jsonrpc)
ExUnit.configure(formatters: [JUnitFormatter, ExUnit.CLIFormatter]) ExUnit.configure(formatters: [JUnitFormatter, ExUnit.CLIFormatter])
ExUnit.start() ExUnit.start()

@ -7,10 +7,6 @@ use Mix.Config
config :ecto, json_library: Jason config :ecto, json_library: Jason
config :explorer, :indexer,
block_rate: 5_000,
debug_logs: !!System.get_env("DEBUG_INDEXER")
# General application configuration # General application configuration
config :explorer, config :explorer,
ecto_repos: [Explorer.Repo], ecto_repos: [Explorer.Repo],

@ -25,7 +25,6 @@ defmodule Explorer.Application do
[ [
configure(Explorer.Chain.Statistics.Server), configure(Explorer.Chain.Statistics.Server),
configure(Explorer.ExchangeRates), configure(Explorer.ExchangeRates),
configure(Explorer.Indexer.Supervisor),
configure(Explorer.Market.History.Cataloger) configure(Explorer.Market.History.Cataloger)
] ]
|> List.flatten() |> List.flatten()

@ -1,26 +0,0 @@
defmodule Explorer.Indexer.Supervisor do
@moduledoc """
Supervising the fetchers for the `Explorer.Indexer`
"""
use Supervisor
alias Explorer.Indexer.{AddressBalanceFetcher, BlockFetcher, InternalTransactionFetcher, PendingTransactionFetcher}
def start_link(opts) do
Supervisor.start_link(__MODULE__, opts, name: __MODULE__)
end
@impl Supervisor
def init(_opts) do
children = [
{Task.Supervisor, name: Explorer.Indexer.TaskSupervisor},
{AddressBalanceFetcher, name: AddressBalanceFetcher},
{PendingTransactionFetcher, name: PendingTransactionFetcher},
{InternalTransactionFetcher, name: InternalTransactionFetcher},
{BlockFetcher, []}
]
Supervisor.init(children, strategy: :one_for_one)
end
end

@ -75,8 +75,6 @@ defmodule Explorer.Mixfile do
# Code coverage # Code coverage
{:excoveralls, "~> 0.8.1", only: [:test]}, {:excoveralls, "~> 0.8.1", only: [:test]},
{:exvcr, "~> 0.10", only: :test}, {:exvcr, "~> 0.10", only: :test},
# JSONRPC access to Parity for `Explorer.Indexer`
{:ethereum_jsonrpc, in_umbrella: true},
{:httpoison, "~> 1.0", override: true}, {:httpoison, "~> 1.0", override: true},
{:jason, "~> 1.0"}, {:jason, "~> 1.0"},
{:junit_formatter, ">= 0.0.0", only: [:test], runtime: false}, {:junit_formatter, ">= 0.0.0", only: [:test], runtime: false},
@ -100,7 +98,7 @@ defmodule Explorer.Mixfile do
[ [
"ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
"ecto.reset": ["ecto.drop", "ecto.setup"], "ecto.reset": ["ecto.drop", "ecto.setup"],
test: ["ecto.create --quiet", "ecto.migrate", "test"] test: ["ecto.create --quiet", "ecto.migrate", "test --no-start"]
] ++ env_aliases(env) ] ++ env_aliases(env)
end end

@ -3,6 +3,9 @@ junit_folder = Mix.Project.build_path() <> "/junit/#{Mix.Project.config()[:app]}
File.mkdir_p!(junit_folder) File.mkdir_p!(junit_folder)
:ok = Application.put_env(:junit_formatter, :report_dir, junit_folder) :ok = Application.put_env(:junit_formatter, :report_dir, junit_folder)
# Counter `test --no-start`. `--no-start` is needed for `:indexer` compatibility
{:ok, _} = Application.ensure_all_started(:explorer)
ExUnit.configure(formatters: [JUnitFormatter, ExUnit.CLIFormatter]) ExUnit.configure(formatters: [JUnitFormatter, ExUnit.CLIFormatter])
ExUnit.start() ExUnit.start()

@ -112,7 +112,13 @@ defmodule ExplorerWeb.Mixfile do
compile: "compile --warnings-as-errors", compile: "compile --warnings-as-errors",
"ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
"ecto.reset": ["ecto.drop", "ecto.setup"], "ecto.reset": ["ecto.drop", "ecto.setup"],
test: ["ecto.create --quiet", "ecto.migrate", "test"] test: [
"ecto.create --quiet",
"ecto.migrate",
# to match behavior of `mix test` from project root, which needs to not start applications for `indexer` to
# prevent its supervision tree from starting, which is undesirable in test
"test --no-start"
]
] ]
end end

@ -3,12 +3,15 @@ junit_folder = Mix.Project.build_path() <> "/junit/#{Mix.Project.config()[:app]}
File.mkdir_p!(junit_folder) File.mkdir_p!(junit_folder)
:ok = Application.put_env(:junit_formatter, :report_dir, junit_folder) :ok = Application.put_env(:junit_formatter, :report_dir, junit_folder)
ExUnit.configure(formatters: [JUnitFormatter, ExUnit.CLIFormatter]) # Counter `test --no-start`. `--no-start` is needed for `:indexer` compatibility
ExUnit.start() {:ok, _} = Application.ensure_all_started(:explorer_web)
{:ok, _} = Application.ensure_all_started(:wallaby) {:ok, _} = Application.ensure_all_started(:wallaby)
Application.put_env(:wallaby, :base_url, ExplorerWeb.Endpoint.url()) Application.put_env(:wallaby, :base_url, ExplorerWeb.Endpoint.url())
{:ok, _} = Application.ensure_all_started(:ex_machina) {:ok, _} = Application.ensure_all_started(:ex_machina)
ExUnit.configure(formatters: [JUnitFormatter, ExUnit.CLIFormatter])
ExUnit.start()
Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, :manual) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, :manual)

@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

@ -0,0 +1,24 @@
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where 3rd-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Ignore package tarball (built via "mix hex.build").
indexer-*.tar

@ -0,0 +1,21 @@
# Indexer
**TODO: Add description**
## Installation
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `indexer` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:indexer, "~> 0.1.0"}
]
end
```
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at [https://hexdocs.pm/indexer](https://hexdocs.pm/indexer).

@ -0,0 +1,9 @@
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config
config :indexer,
block_rate: 5_000,
debug_logs: !!System.get_env("DEBUG_INDEXER")
config :indexer, ecto_repos: [Explorer.Repo]

@ -1,10 +1,10 @@
defmodule Explorer.Indexer do defmodule Indexer do
@moduledoc """ @moduledoc """
Indexes an Ethereum-based chain using JSONRPC. Indexes an Ethereum-based chain using JSONRPC.
""" """
require Logger require Logger
alias Explorer.{Chain, Indexer} alias Explorer.Chain
@doc """ @doc """
The maximum `t:Explorer.Chain.Block.t/0` `number` that was indexed The maximum `t:Explorer.Chain.Block.t/0` `number` that was indexed
@ -13,12 +13,12 @@ defmodule Explorer.Indexer do
iex> insert(:block, number: 2) iex> insert(:block, number: 2)
iex> insert(:block, number: 1) iex> insert(:block, number: 1)
iex> Explorer.Indexer.max_block_number() iex> Indexer.max_block_number()
2 2
If there are no blocks, `0` is returned to indicate to index from genesis block. If there are no blocks, `0` is returned to indicate to index from genesis block.
iex> Explorer.Indexer.max_block_number() iex> Indexer.max_block_number()
0 0
""" """
@ -34,16 +34,16 @@ defmodule Explorer.Indexer do
When there are no blocks the next block is the 0th block When there are no blocks the next block is the 0th block
iex> Explorer.Indexer.max_block_number() iex> Indexer.max_block_number()
0 0
iex> Explorer.Indexer.next_block_number() iex> Indexer.next_block_number()
0 0
When there is a block, it is the successive block number When there is a block, it is the successive block number
iex> insert(:block, number: 2) iex> insert(:block, number: 2)
iex> insert(:block, number: 1) iex> insert(:block, number: 1)
iex> Explorer.Indexer.next_block_number() iex> Indexer.next_block_number()
3 3
""" """
@ -69,14 +69,14 @@ defmodule Explorer.Indexer do
Enables debug logs for indexing system. Enables debug logs for indexing system.
""" """
def enable_debug_logs do def enable_debug_logs do
Application.put_env(:explorer, :indexer, Keyword.put(config(), :debug_logs, true)) Application.put_env(:indexer, :debug_logs, true)
end end
@doc """ @doc """
Disables debug logs for indexing system. Disables debug logs for indexing system.
""" """
def disable_debug_logs do def disable_debug_logs do
Application.put_env(:explorer, :indexer, Keyword.put(config(), :debug_logs, false)) Application.put_env(:indexer, :debug_logs, false)
end end
@doc """ @doc """
@ -90,8 +90,6 @@ defmodule Explorer.Indexer do
end end
defp debug_logs_enabled? do defp debug_logs_enabled? do
Keyword.fetch!(config(), :debug_logs) Application.fetch_env!(:indexer, :debug_logs)
end end
defp config, do: Application.fetch_env!(:explorer, :indexer)
end end

@ -1,13 +1,13 @@
defmodule Explorer.Indexer.AddressBalanceFetcher do defmodule Indexer.AddressBalanceFetcher do
@moduledoc """ @moduledoc """
Fetches `t:Explorer.Chain.Address.t/0` `fetched_balance`. Fetches `t:Explorer.Chain.Address.t/0` `fetched_balance`.
""" """
import EthereumJSONRPC, only: [integer_to_quantity: 1] import EthereumJSONRPC, only: [integer_to_quantity: 1]
alias Explorer.{BufferedTask, Chain} alias Explorer.Chain
alias Explorer.Chain.{Block, Hash} alias Explorer.Chain.{Block, Hash}
alias Explorer.Indexer alias Indexer.BufferedTask
@behaviour BufferedTask @behaviour BufferedTask
@ -16,7 +16,7 @@ defmodule Explorer.Indexer.AddressBalanceFetcher do
max_batch_size: 500, max_batch_size: 500,
max_concurrency: 4, max_concurrency: 4,
init_chunk_size: 1000, init_chunk_size: 1000,
task_supervisor: Explorer.Indexer.TaskSupervisor task_supervisor: Indexer.TaskSupervisor
] ]
@doc """ @doc """

@ -1,4 +1,4 @@
defmodule Explorer.Indexer.AddressExtraction do defmodule Indexer.AddressExtraction do
@moduledoc """ @moduledoc """
Extract Addresses from data fetched from the Blockchain and structured as Blocks, InternalTransactions, Extract Addresses from data fetched from the Blockchain and structured as Blocks, InternalTransactions,
Transactions and Logs. Transactions and Logs.
@ -104,7 +104,7 @@ defmodule Explorer.Indexer.AddressExtraction do
Blocks have their `miner_hash` extracted. Blocks have their `miner_hash` extracted.
iex> Explorer.Indexer.AddressExtraction.extract_addresses( iex> Indexer.AddressExtraction.extract_addresses(
...> %{ ...> %{
...> blocks: [ ...> blocks: [
...> %{ ...> %{
@ -124,7 +124,7 @@ defmodule Explorer.Indexer.AddressExtraction do
Internal transactions can have their `from_address_hash`, `to_address_hash` and/or `created_contract_address_hash` Internal transactions can have their `from_address_hash`, `to_address_hash` and/or `created_contract_address_hash`
extracted. extracted.
iex> Explorer.Indexer.AddressExtraction.extract_addresses( iex> Indexer.AddressExtraction.extract_addresses(
...> %{ ...> %{
...> internal_transactions: [ ...> internal_transactions: [
...> %{ ...> %{
@ -161,7 +161,7 @@ defmodule Explorer.Indexer.AddressExtraction do
Transactions can have their `from_address_hash` and/or `to_address_hash` extracted. Transactions can have their `from_address_hash` and/or `to_address_hash` extracted.
iex> Explorer.Indexer.AddressExtraction.extract_addresses( iex> Indexer.AddressExtraction.extract_addresses(
...> %{ ...> %{
...> transactions: [ ...> transactions: [
...> %{ ...> %{
@ -193,7 +193,7 @@ defmodule Explorer.Indexer.AddressExtraction do
Logs can have their `address_hash` extracted. Logs can have their `address_hash` extracted.
iex> Explorer.Indexer.AddressExtraction.extract_addresses( iex> Indexer.AddressExtraction.extract_addresses(
...> %{ ...> %{
...> logs: [ ...> logs: [
...> %{ ...> %{
@ -212,7 +212,7 @@ defmodule Explorer.Indexer.AddressExtraction do
When the same address is mentioned multiple times, the greatest `block_number` is used When the same address is mentioned multiple times, the greatest `block_number` is used
iex> Explorer.Indexer.AddressExtraction.extract_addresses( iex> Indexer.AddressExtraction.extract_addresses(
...> %{ ...> %{
...> blocks: [ ...> blocks: [
...> %{ ...> %{
@ -262,7 +262,7 @@ defmodule Explorer.Indexer.AddressExtraction do
When a contract is created and then used in internal transactions and transaction in the same fetched data, the When a contract is created and then used in internal transactions and transaction in the same fetched data, the
`created_contract_code` is merged with the greatest `block_number` `created_contract_code` is merged with the greatest `block_number`
iex> Explorer.Indexer.AddressExtraction.extract_addresses( iex> Indexer.AddressExtraction.extract_addresses(
...> %{ ...> %{
...> internal_transactions: [ ...> internal_transactions: [
...> %{ ...> %{

@ -0,0 +1,24 @@
defmodule Indexer.Application do
@moduledoc """
This is the `Application` module for `Indexer`.
"""
use Application
alias Indexer.{AddressBalanceFetcher, BlockFetcher, InternalTransactionFetcher, PendingTransactionFetcher}
@impl Application
def start(_type, _args) do
children = [
{Task.Supervisor, name: Indexer.TaskSupervisor},
{AddressBalanceFetcher, name: AddressBalanceFetcher},
{PendingTransactionFetcher, name: PendingTransactionFetcher},
{InternalTransactionFetcher, name: InternalTransactionFetcher},
{BlockFetcher, []}
]
opts = [strategy: :one_for_one, name: Indexer.Supervisor]
Supervisor.start_link(children, opts)
end
end

@ -1,4 +1,4 @@
defmodule Explorer.Indexer.BlockFetcher do defmodule Indexer.BlockFetcher do
@moduledoc """ @moduledoc """
Fetches and indexes block ranges from gensis to realtime. Fetches and indexes block ranges from gensis to realtime.
""" """
@ -7,12 +7,12 @@ defmodule Explorer.Indexer.BlockFetcher do
require Logger require Logger
import Explorer.Indexer, only: [debug: 1] import Indexer, only: [debug: 1]
alias EthereumJSONRPC alias EthereumJSONRPC
alias EthereumJSONRPC.Transactions alias EthereumJSONRPC.Transactions
alias Explorer.{BufferedTask, Chain, Indexer} alias Explorer.Chain
alias Explorer.Indexer.{AddressBalanceFetcher, AddressExtraction, InternalTransactionFetcher, Sequence} alias Indexer.{AddressBalanceFetcher, AddressExtraction, BufferedTask, InternalTransactionFetcher, Sequence}
# dialyzer thinks that Logger.debug functions always have no_local_return # dialyzer thinks that Logger.debug functions always have no_local_return
@dialyzer {:nowarn_function, import_range: 3} @dialyzer {:nowarn_function, import_range: 3}
@ -63,7 +63,11 @@ defmodule Explorer.Indexer.BlockFetcher do
@impl GenServer @impl GenServer
def init(opts) do def init(opts) do
opts = Keyword.merge(Application.fetch_env!(:explorer, :indexer), opts) opts =
:indexer
|> Application.get_all_env()
|> Keyword.merge(opts)
:timer.send_interval(15_000, self(), :debug_count) :timer.send_interval(15_000, self(), :debug_count)
state = %{ state = %{

@ -1,4 +1,4 @@
defmodule Explorer.BufferedTask do defmodule Indexer.BufferedTask do
@moduledoc """ @moduledoc """
Provides a behaviour for batched task running with retries. Provides a behaviour for batched task running with retries.
@ -166,7 +166,7 @@ defmodule Explorer.BufferedTask do
]} ]}
) :: {:ok, pid()} | {:error, {:already_started, pid()}} ) :: {:ok, pid()} | {:error, {:already_started, pid()}}
def start_link({module, base_opts}) do def start_link({module, base_opts}) do
default_opts = Application.fetch_env!(:explorer, :indexer) default_opts = Application.get_all_env(:indexer)
opts = Keyword.merge(default_opts, base_opts) opts = Keyword.merge(default_opts, base_opts)
GenServer.start_link(__MODULE__, {module, opts}, name: opts[:name]) GenServer.start_link(__MODULE__, {module, opts}, name: opts[:name])

@ -1,4 +1,4 @@
defmodule Explorer.Indexer.InternalTransactionFetcher do defmodule Indexer.InternalTransactionFetcher do
@moduledoc """ @moduledoc """
Fetches and indexes `t:Explorer.Chain.InternalTransaction.t/0`. Fetches and indexes `t:Explorer.Chain.InternalTransaction.t/0`.
@ -7,8 +7,8 @@ defmodule Explorer.Indexer.InternalTransactionFetcher do
require Logger require Logger
alias Explorer.{BufferedTask, Chain, Indexer} alias Explorer.Chain
alias Explorer.Indexer.{AddressBalanceFetcher, AddressExtraction} alias Indexer.{AddressBalanceFetcher, AddressExtraction, BufferedTask}
alias Explorer.Chain.{Block, Hash} alias Explorer.Chain.{Block, Hash}
@behaviour BufferedTask @behaviour BufferedTask
@ -20,7 +20,7 @@ defmodule Explorer.Indexer.InternalTransactionFetcher do
max_concurrency: @max_concurrency, max_concurrency: @max_concurrency,
max_batch_size: @max_batch_size, max_batch_size: @max_batch_size,
init_chunk_size: 5000, init_chunk_size: 5000,
task_supervisor: Explorer.Indexer.TaskSupervisor task_supervisor: Indexer.TaskSupervisor
] ]
@doc """ @doc """

@ -1,4 +1,4 @@
defmodule Explorer.Indexer.PendingTransactionFetcher do defmodule Indexer.PendingTransactionFetcher do
@moduledoc """ @moduledoc """
Fetches pending transactions and imports them. Fetches pending transactions and imports them.
@ -11,8 +11,8 @@ defmodule Explorer.Indexer.PendingTransactionFetcher do
import EthereumJSONRPC.Parity, only: [fetch_pending_transactions: 0] import EthereumJSONRPC.Parity, only: [fetch_pending_transactions: 0]
alias Explorer.{Chain, Indexer} alias Explorer.Chain
alias Explorer.Indexer.{AddressExtraction, PendingTransactionFetcher} alias Indexer.{AddressExtraction, PendingTransactionFetcher}
# milliseconds # milliseconds
@default_interval 1_000 @default_interval 1_000
@ -46,8 +46,8 @@ defmodule Explorer.Indexer.PendingTransactionFetcher do
@impl GenServer @impl GenServer
def init(opts) do def init(opts) do
opts = opts =
:explorer :indexer
|> Application.fetch_env!(:indexer) |> Application.get_all_env()
|> Keyword.merge(opts) |> Keyword.merge(opts)
state = state =

@ -1,4 +1,4 @@
defmodule Explorer.Indexer.Sequence do defmodule Indexer.Sequence do
@moduledoc false @moduledoc false
use Agent use Agent

@ -0,0 +1,49 @@
defmodule Indexer.MixProject do
use Mix.Project
def project do
[
aliases: aliases(),
app: :indexer,
version: "0.1.0",
build_path: "../../_build",
config_path: "../../config/config.exs",
deps_path: "../../deps",
lockfile: "../../mix.lock",
elixir: "~> 1.6",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger],
mod: {Indexer.Application, []}
]
end
defp aliases do
[
# so that the supervision tree does not start, which would begin indexing, and so that the various fetchers can
# be started with `ExUnit`'s `start_supervised` for unit testing.
test: "test --no-start"
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
# JSONRPC access to Parity for `Explorer.Indexer`
{:ethereum_jsonrpc, in_umbrella: true},
# Importing to database
{:explorer, in_umbrella: true}
]
end
# Specifies which paths to compile per environment.
defp elixirc_paths(:test), do: ["test/support" | elixirc_paths(:dev)]
defp elixirc_paths(_), do: ["lib"]
end

@ -1,10 +1,10 @@
defmodule Explorer.Indexer.AddressBalanceFetcherTest do defmodule Indexer.AddressBalanceFetcherTest do
# MUST be `async: false` so that {:shared, pid} is set for connection to allow AddressBalanceFetcher's self-send to have # MUST be `async: false` so that {:shared, pid} is set for connection to allow AddressBalanceFetcher's self-send to have
# connection allowed immediately. # connection allowed immediately.
use Explorer.DataCase, async: false use Explorer.DataCase, async: false
alias Explorer.Chain.{Address, Hash, Wei} alias Explorer.Chain.{Address, Hash, Wei}
alias Explorer.Indexer.{AddressBalanceFetcher, AddressBalanceFetcherCase} alias Indexer.{AddressBalanceFetcher, AddressBalanceFetcherCase}
@block_number 2_932_838 @block_number 2_932_838
@hash %Explorer.Chain.Hash{ @hash %Explorer.Chain.Hash{
@ -13,7 +13,7 @@ defmodule Explorer.Indexer.AddressBalanceFetcherTest do
} }
setup do setup do
start_supervised!({Task.Supervisor, name: Explorer.Indexer.TaskSupervisor}) start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor})
:ok :ok
end end

@ -1,13 +1,13 @@
defmodule Explorer.Indexer.AddressExtractionTest do defmodule Indexer.AddressExtractionTest do
use Explorer.DataCase, async: true use Explorer.DataCase, async: true
alias Explorer.Indexer.AddressExtraction alias Indexer.AddressExtraction
doctest AddressExtraction doctest AddressExtraction
describe "extract_addresses/1" do describe "extract_addresses/1" do
test "blocks without a `miner_hash` aren't extracted" do test "blocks without a `miner_hash` aren't extracted" do
assert Explorer.Indexer.AddressExtraction.extract_addresses(%{ assert Indexer.AddressExtraction.extract_addresses(%{
blocks: [ blocks: [
%{ %{
number: 34 number: 34
@ -17,7 +17,7 @@ defmodule Explorer.Indexer.AddressExtractionTest do
end end
test "blocks without a `number` aren't extracted" do test "blocks without a `number` aren't extracted" do
assert Explorer.Indexer.AddressExtraction.extract_addresses(%{ assert Indexer.AddressExtraction.extract_addresses(%{
blocks: [ blocks: [
%{ %{
miner_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca" miner_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca"
@ -27,7 +27,7 @@ defmodule Explorer.Indexer.AddressExtractionTest do
end end
test "internal_transactions with a `from_address_hash` without a `block_number` aren't extracted" do test "internal_transactions with a `from_address_hash` without a `block_number` aren't extracted" do
assert Explorer.Indexer.AddressExtraction.extract_addresses(%{ assert Indexer.AddressExtraction.extract_addresses(%{
internal_transactions: [ internal_transactions: [
%{ %{
from_address_hash: "0x0000000000000000000000000000000000000001" from_address_hash: "0x0000000000000000000000000000000000000001"
@ -37,7 +37,7 @@ defmodule Explorer.Indexer.AddressExtractionTest do
end end
test "internal_transactions with a `to_address_hash` without a `block_number` aren't extracted" do test "internal_transactions with a `to_address_hash` without a `block_number` aren't extracted" do
assert Explorer.Indexer.AddressExtraction.extract_addresses(%{ assert Indexer.AddressExtraction.extract_addresses(%{
internal_transactions: [ internal_transactions: [
%{ %{
to_address_hash: "0x0000000000000000000000000000000000000002" to_address_hash: "0x0000000000000000000000000000000000000002"
@ -48,7 +48,7 @@ defmodule Explorer.Indexer.AddressExtractionTest do
test "internal_transactions with a `created_contract_address_hash` and `created_contract_code` " <> test "internal_transactions with a `created_contract_address_hash` and `created_contract_code` " <>
"without a `block_number` aren't extracted" do "without a `block_number` aren't extracted" do
assert Explorer.Indexer.AddressExtraction.extract_addresses(%{ assert Indexer.AddressExtraction.extract_addresses(%{
internal_transactions: [ internal_transactions: [
%{ %{
created_contract_address_hash: "0x0000000000000000000000000000000000000003", created_contract_address_hash: "0x0000000000000000000000000000000000000003",
@ -59,7 +59,7 @@ defmodule Explorer.Indexer.AddressExtractionTest do
end end
test "differing contract code is ignored" do test "differing contract code is ignored" do
assert Explorer.Indexer.AddressExtraction.extract_addresses(%{ assert Indexer.AddressExtraction.extract_addresses(%{
internal_transactions: [ internal_transactions: [
%{ %{
block_number: 1, block_number: 1,

@ -1,16 +1,16 @@
defmodule Explorer.Indexer.BlockFetcherTest do defmodule Indexer.BlockFetcherTest do
# `async: false` due to use of named GenServer # `async: false` due to use of named GenServer
use Explorer.DataCase, async: false use Explorer.DataCase, async: false
import ExUnit.CaptureLog import ExUnit.CaptureLog
alias Explorer.Chain.{Address, Block, Log, Transaction, Wei} alias Explorer.Chain.{Address, Block, Log, Transaction, Wei}
alias Explorer.Indexer
alias Explorer.Indexer.{ alias Indexer.{
AddressBalanceFetcher, AddressBalanceFetcher,
AddressBalanceFetcherCase, AddressBalanceFetcherCase,
BlockFetcher, BlockFetcher,
BufferedTask,
InternalTransactionFetcher, InternalTransactionFetcher,
InternalTransactionFetcherCase, InternalTransactionFetcherCase,
Sequence Sequence
@ -46,7 +46,7 @@ defmodule Explorer.Indexer.BlockFetcherTest do
assert Repo.aggregate(Block, :count, :hash) == 0 assert Repo.aggregate(Block, :count, :hash) == 0
start_supervised!({Task.Supervisor, name: Explorer.Indexer.TaskSupervisor}) start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor})
AddressBalanceFetcherCase.start_supervised!() AddressBalanceFetcherCase.start_supervised!()
InternalTransactionFetcherCase.start_supervised!() InternalTransactionFetcherCase.start_supervised!()
start_supervised!(BlockFetcher) start_supervised!(BlockFetcher)
@ -89,7 +89,7 @@ defmodule Explorer.Indexer.BlockFetcherTest do
@tag :capture_log @tag :capture_log
@heading "persisted counts" @heading "persisted counts"
test "without debug_logs", %{state: state} do test "without debug_logs", %{state: state} do
start_supervised!({Task.Supervisor, name: Explorer.Indexer.TaskSupervisor}) start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor})
AddressBalanceFetcherCase.start_supervised!() AddressBalanceFetcherCase.start_supervised!()
InternalTransactionFetcherCase.start_supervised!() InternalTransactionFetcherCase.start_supervised!()
@ -104,7 +104,7 @@ defmodule Explorer.Indexer.BlockFetcherTest do
@tag :capture_log @tag :capture_log
test "with debug_logs", %{state: state} do test "with debug_logs", %{state: state} do
start_supervised!({Task.Supervisor, name: Explorer.Indexer.TaskSupervisor}) start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor})
AddressBalanceFetcherCase.start_supervised!() AddressBalanceFetcherCase.start_supervised!()
InternalTransactionFetcherCase.start_supervised!() InternalTransactionFetcherCase.start_supervised!()
@ -129,7 +129,7 @@ defmodule Explorer.Indexer.BlockFetcherTest do
setup :state setup :state
setup do setup do
start_supervised!({Task.Supervisor, name: Explorer.Indexer.TaskSupervisor}) start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor})
AddressBalanceFetcherCase.start_supervised!() AddressBalanceFetcherCase.start_supervised!()
InternalTransactionFetcherCase.start_supervised!() InternalTransactionFetcherCase.start_supervised!()
{:ok, state} = BlockFetcher.init([]) {:ok, state} = BlockFetcher.init([])
@ -293,7 +293,7 @@ defmodule Explorer.Indexer.BlockFetcherTest do
defp wait_for_tasks(buffered_task) do defp wait_for_tasks(buffered_task) do
wait_until(5000, fn -> wait_until(5000, fn ->
counts = Explorer.BufferedTask.debug_count(buffered_task) counts = BufferedTask.debug_count(buffered_task)
counts.buffer == 0 and counts.tasks == 0 counts.buffer == 0 and counts.tasks == 0
end) end)
end end

@ -1,7 +1,7 @@
defmodule Explorer.BufferedTaskTest do defmodule Indexer.BufferedTaskTest do
use ExUnit.Case, async: true use ExUnit.Case, async: true
alias Explorer.BufferedTask alias Indexer.BufferedTask
@max_batch_size 2 @max_batch_size 2

@ -1,15 +1,15 @@
defmodule Explorer.Indexer.InternalTransactionFetcherTest do defmodule Indexer.InternalTransactionFetcherTest do
use Explorer.DataCase, async: false use Explorer.DataCase, async: false
import ExUnit.CaptureLog import ExUnit.CaptureLog
alias Explorer.Chain.Transaction alias Explorer.Chain.Transaction
alias Explorer.Indexer.{AddressBalanceFetcherCase, InternalTransactionFetcher, PendingTransactionFetcher} alias Indexer.{AddressBalanceFetcherCase, InternalTransactionFetcher, PendingTransactionFetcher}
@moduletag :capture_log @moduletag :capture_log
test "does not try to fetch pending transactions from Explorer.Indexer.PendingTransactionFetcher" do test "does not try to fetch pending transactions from Indexer.PendingTransactionFetcher" do
start_supervised!({Task.Supervisor, name: Explorer.Indexer.TaskSupervisor}) start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor})
AddressBalanceFetcherCase.start_supervised!() AddressBalanceFetcherCase.start_supervised!()
start_supervised!(PendingTransactionFetcher) start_supervised!(PendingTransactionFetcher)
@ -57,7 +57,7 @@ defmodule Explorer.Indexer.InternalTransactionFetcherTest do
describe "run/2" do describe "run/2" do
test "duplicate transaction hashes are logged" do test "duplicate transaction hashes are logged" do
start_supervised!({Task.Supervisor, name: Explorer.Indexer.TaskSupervisor}) start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor})
AddressBalanceFetcherCase.start_supervised!() AddressBalanceFetcherCase.start_supervised!()
insert(:transaction, hash: "0x03cd5899a63b6f6222afda8705d059fd5a7d126bcabe962fb654d9736e6bcafa") insert(:transaction, hash: "0x03cd5899a63b6f6222afda8705d059fd5a7d126bcabe962fb654d9736e6bcafa")
@ -82,7 +82,7 @@ defmodule Explorer.Indexer.InternalTransactionFetcherTest do
end end
test "duplicate transaction hashes only retry uniques" do test "duplicate transaction hashes only retry uniques" do
start_supervised!({Task.Supervisor, name: Explorer.Indexer.TaskSupervisor}) start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor})
AddressBalanceFetcherCase.start_supervised!() AddressBalanceFetcherCase.start_supervised!()
# not a real transaction hash, so that it fails # not a real transaction hash, so that it fails

@ -1,16 +1,16 @@
defmodule Explorer.Indexer.PendingTransactionFetcherTest do defmodule Indexer.PendingTransactionFetcherTest do
# `async: false` due to use of named GenServer # `async: false` due to use of named GenServer
use Explorer.DataCase, async: false use Explorer.DataCase, async: false
alias Explorer.Chain.Transaction alias Explorer.Chain.Transaction
alias Explorer.Indexer.PendingTransactionFetcher alias Indexer.PendingTransactionFetcher
describe "start_link/1" do describe "start_link/1" do
# this test may fail if Sokol so low volume that the pending transactions are empty for too long # this test may fail if Sokol so low volume that the pending transactions are empty for too long
test "starts fetching pending transactions" do test "starts fetching pending transactions" do
assert Repo.aggregate(Transaction, :count, :hash) == 0 assert Repo.aggregate(Transaction, :count, :hash) == 0
start_supervised!({Task.Supervisor, name: Explorer.Indexer.TaskSupervisor}) start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor})
start_supervised!(PendingTransactionFetcher) start_supervised!(PendingTransactionFetcher)
wait_for_results(fn -> wait_for_results(fn ->

@ -1,7 +1,7 @@
defmodule Explorer.Indexer.SequenceTest do defmodule Indexer.SequenceTest do
use ExUnit.Case use ExUnit.Case
alias Explorer.Indexer.Sequence alias Indexer.Sequence
test "start_link" do test "start_link" do
{:ok, pid} = Sequence.start_link([1..4], 5, 1) {:ok, pid} = Sequence.start_link([1..4], 5, 1)

@ -1,8 +1,6 @@
defmodule Explorer.IndexerTest do defmodule IndexerTest do
use Explorer.DataCase, async: true use Explorer.DataCase, async: true
alias Explorer.Indexer
import Explorer.Factory import Explorer.Factory
doctest Indexer doctest Indexer

@ -1,5 +1,5 @@
defmodule Explorer.Indexer.AddressBalanceFetcherCase do defmodule Indexer.AddressBalanceFetcherCase do
alias Explorer.Indexer.AddressBalanceFetcher alias Indexer.AddressBalanceFetcher
def start_supervised!(options \\ []) when is_list(options) do def start_supervised!(options \\ []) when is_list(options) do
options options

@ -1,5 +1,5 @@
defmodule Explorer.Indexer.InternalTransactionFetcherCase do defmodule Indexer.InternalTransactionFetcherCase do
alias Explorer.Indexer.InternalTransactionFetcher alias Indexer.InternalTransactionFetcher
def start_supervised!(options \\ []) when is_list(options) do def start_supervised!(options \\ []) when is_list(options) do
options options

@ -0,0 +1,20 @@
# https://github.com/CircleCI-Public/circleci-demo-elixir-phoenix/blob/a89de33a01df67b6773ac90adc74c34367a4a2d6/test/test_helper.exs#L1-L3
junit_folder = Mix.Project.build_path() <> "/junit/#{Mix.Project.config()[:app]}"
File.mkdir_p!(junit_folder)
:ok = Application.put_env(:junit_formatter, :report_dir, junit_folder)
# start all dependencies, but not Indexer itself as we need to unit test the supervision tree without and don't want the
# genesis task scanning in the background
Application.load(:indexer)
for application <- Application.spec(:indexer, :applications) do
Application.ensure_all_started(application)
end
# no declared in :applications since it is test-only
{:ok, _} = Application.ensure_all_started(:ex_machina)
ExUnit.configure(formatters: [JUnitFormatter, ExUnit.CLIFormatter])
ExUnit.start()
Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, :manual)

@ -1,7 +1,7 @@
{ {
"coverage_options": { "coverage_options": {
"treat_no_relevant_lines_as_covered": true, "treat_no_relevant_lines_as_covered": true,
"minimum_coverage": 93.7 "minimum_coverage": 94.4
}, },
"terminal_options": { "terminal_options": {
"file_column_width": 120 "file_column_width": 120

@ -28,11 +28,19 @@ defmodule ExplorerUmbrella.Mixfile do
## Private Functions ## Private Functions
defp aliases(:dev) do defp aliases(env) do
[
# to match behavior of `mix test` in `apps/indexer`, which needs to not start applications for `indexer` to
# prevent its supervision tree from starting, which is undesirable in test
test: "test --no-start"
] ++ env_aliases(env)
end
defp env_aliases(:dev) do
[] []
end end
defp aliases(_env) do defp env_aliases(_env) do
[ [
compile: "compile --warnings-as-errors" compile: "compile --warnings-as-errors"
] ]

Loading…
Cancel
Save