diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index cd3303320f..751929b65c 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -14,8 +14,6 @@ config :explorer, config :explorer, Explorer.Integrations.EctoLogger, query_time_ms_threshold: 2_000 -config :explorer, Explorer.Chain.Statistics.Server, enabled: true - config :explorer, Explorer.ExchangeRates, enabled: true config :explorer, Explorer.Market.History.Cataloger, enabled: true diff --git a/apps/explorer/config/test.exs b/apps/explorer/config/test.exs index 82f3cad908..5b7d3ce164 100644 --- a/apps/explorer/config/test.exs +++ b/apps/explorer/config/test.exs @@ -13,8 +13,6 @@ config :explorer, Explorer.Repo, pool_timeout: 10_000, ownership_timeout: 60_000 -config :explorer, Explorer.Chain.Statistics.Server, enabled: false - config :explorer, Explorer.ExchangeRates, enabled: false config :explorer, Explorer.Market.History.Cataloger, enabled: false diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index f10f2e7115..3b51056942 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -24,7 +24,6 @@ defmodule Explorer.Application do defp configurable_children do [ - configure(Explorer.Chain.Statistics.Server), configure(Explorer.ExchangeRates), configure(Explorer.Market.History.Cataloger) ] diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 3e0133ad4b..8d94dee0ff 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -188,6 +188,31 @@ defmodule Explorer.Chain do |> Repo.all() end + + @doc """ + The average time it took to mine/validate the last <= 100 `t:Explorer.Chain.Block.t/0` + """ + @spec average_block_time() :: non_neg_integer() + def average_block_time() do + {:ok, %Postgrex.Result{rows: [[rows]]}} = + SQL.query( + Repo, + """ + SELECT coalesce(avg(difference), interval '0 seconds') + FROM ( + SELECT b.timestamp - lag(b.timestamp) over (order by b.timestamp) as difference + FROM (SELECT * FROM blocks ORDER BY number DESC LIMIT 101) b + LIMIT 100 OFFSET 1 + ) t + """, + [] + ) + + {:ok, value} = Timex.Ecto.Time.load(rows) + + value + end + @doc """ The `t:Explorer.Chain.Address.t/0` `balance` in `unit`. """ diff --git a/apps/explorer/lib/explorer/chain/statistics.ex b/apps/explorer/lib/explorer/chain/statistics.ex deleted file mode 100644 index cffa528805..0000000000 --- a/apps/explorer/lib/explorer/chain/statistics.ex +++ /dev/null @@ -1,104 +0,0 @@ -defmodule Explorer.Chain.Statistics do - @moduledoc """ - Represents statistics about the chain. - """ - - import Ecto.Query - - alias Ecto.Adapters.SQL - alias Explorer.{Chain, PagingOptions, Repo} - alias Explorer.Chain.{Block, Transaction} - alias Timex.Duration - - @average_time_query """ - SELECT coalesce(avg(difference), interval '0 seconds') - FROM ( - SELECT b.timestamp - lag(b.timestamp) over (order by b.timestamp) as difference - FROM (SELECT * FROM blocks ORDER BY number DESC LIMIT 101) b - LIMIT 100 OFFSET 1 - ) t - """ - - @typedoc """ - The number of `t:Explorer.Chain.Block.t/0` mined/validated per minute. - """ - @type blocks_per_minute :: non_neg_integer() - - @typedoc """ - * `average_time` - the average time it took to mine/validate the last <= 100 `t:Explorer.Chain.Block.t/0` - * `blocks` - the last <= 5 `t:Explorer.Chain.Block.t/0` - (`t:Explorer.Chain.Block.t/0` `timestamp`) and when it was inserted into the databasse - (`t:Explorer.Chain.Block.t/0` `inserted_at`) - * `number` - the latest `t:Explorer.Chain.Block.t/0` `number` - `t:Explorer.Chain.Block.t/0` - * `timestamp` - when the last `t:Explorer.Chain.Block.t/0` was mined/validated - * `transaction_velocity` - the number of `t:Explorer.Chain.Block.t/0` mined/validated in the last minute - * `transactions` - the last <= 5 `t:Explorer.Chain.Transaction.t/0` - """ - @type t :: %__MODULE__{ - average_time: Duration.t(), - blocks: [Block.t()], - number: Block.block_number(), - timestamp: :calendar.datetime(), - transactions: [Transaction.t()] - } - - defstruct average_time: %Duration{seconds: 0, megaseconds: 0, microseconds: 0}, - blocks: [], - number: -1, - timestamp: nil, - transactions: [] - - def fetch do - blocks = - from( - block in Block, - order_by: [desc: block.number], - preload: [:miner, :transactions], - limit: 4 - ) - - transactions = - Chain.recent_collated_transactions( - necessity_by_association: %{ - block: :required, - from_address: :required, - to_address: :optional - }, - paging_options: %PagingOptions{page_size: 5} - ) - - %__MODULE__{ - average_time: query_duration(@average_time_query), - blocks: Repo.all(blocks), - transactions: transactions - } - |> put_max_numbered_block() - end - - defp put_max_numbered_block(state) do - case Chain.max_numbered_block() do - {:ok, %Block{number: number, timestamp: timestamp}} -> - %__MODULE__{ - state - | number: number, - timestamp: timestamp - } - - {:error, :not_found} -> - state - end - end - - defp query_duration(query) do - results = SQL.query!(Repo, query, []) - - {:ok, value} = - results.rows - |> List.first() - |> List.first() - |> Timex.Ecto.Time.load() - - value - end -end diff --git a/apps/explorer/lib/explorer/chain/statistics/server.ex b/apps/explorer/lib/explorer/chain/statistics/server.ex deleted file mode 100644 index a2ed3c58e6..0000000000 --- a/apps/explorer/lib/explorer/chain/statistics/server.ex +++ /dev/null @@ -1,77 +0,0 @@ -defmodule Explorer.Chain.Statistics.Server do - @moduledoc "Stores the latest chain statistics." - - use GenServer - - require Logger - - alias Explorer.Chain.Statistics - - @interval 1_000 - - defstruct statistics: %Statistics{}, - task: nil - - def child_spec(_) do - Supervisor.Spec.worker(__MODULE__, [[refresh: true]]) - end - - @spec fetch() :: Statistics.t() - def fetch do - GenServer.call(__MODULE__, :fetch) - end - - def start_link(opts \\ []) do - GenServer.start_link(__MODULE__, opts, name: __MODULE__) - end - - @impl GenServer - def init(options) when is_list(options) do - if Keyword.get(options, :refresh, true) do - send(self(), :refresh) - end - - {:ok, %__MODULE__{}} - end - - @impl GenServer - - def handle_info(:refresh, %__MODULE__{task: task} = state) do - new_state = - case task do - nil -> - %__MODULE__{state | task: Task.Supervisor.async_nolink(Explorer.TaskSupervisor, Statistics, :fetch, [])} - - _ -> - state - end - - {:noreply, new_state} - end - - def handle_info({ref, %Statistics{} = statistics}, %__MODULE__{task: %Task{ref: ref}} = state) do - Process.demonitor(ref, [:flush]) - Process.send_after(self(), :refresh, @interval) - - {:noreply, %__MODULE__{state | statistics: statistics, task: nil}} - end - - def handle_info({:DOWN, ref, :process, pid, reason}, %__MODULE__{task: %Task{pid: pid, ref: ref}} = state) do - Logger.error(fn -> "#{inspect(Statistics)}.fetch failed and could not be cached: #{inspect(reason)}" end) - - Process.send_after(self(), :refresh, @interval) - - {:noreply, %__MODULE__{state | task: nil}} - end - - @impl GenServer - def handle_call(:fetch, _, %__MODULE__{statistics: %Statistics{} = statistics} = state), - do: {:reply, statistics, state} - - @impl GenServer - def terminate(_, %__MODULE__{task: nil}), do: :ok - - def terminate(_, %__MODULE__{task: task}) do - Task.shutdown(task) - end -end diff --git a/apps/explorer/test/explorer/chain/statistics/server_test.exs b/apps/explorer/test/explorer/chain/statistics/server_test.exs deleted file mode 100644 index 251bf01e0f..0000000000 --- a/apps/explorer/test/explorer/chain/statistics/server_test.exs +++ /dev/null @@ -1,120 +0,0 @@ -defmodule Explorer.Chain.Statistics.ServerTest do - use Explorer.DataCase, async: false - - import ExUnit.CaptureLog - - alias Explorer.Chain.Statistics - alias Explorer.Chain.Statistics.Server - - # shutdown: "owner exited with: shutdown" error from polluting logs when tests are successful - @moduletag :capture_log - - describe "child_spec/1" do - test "it defines a child_spec/1 that works with supervisors" do - insert(:block) - - assert {:ok, pid} = start_supervised(Server) - - %Server{task: %Task{pid: pid}} = :sys.get_state(pid) - ref = Process.monitor(pid) - - assert_receive {:DOWN, ^ref, :process, ^pid, _} - end - end - - describe "init/1" do - test "returns a new chain when not told to refresh" do - empty_statistics = %Statistics{} - - assert {:ok, %Server{statistics: ^empty_statistics}} = Server.init(refresh: false) - end - - test "returns a new Statistics when told to refresh" do - empty_statistics = %Statistics{} - - assert {:ok, %Server{statistics: ^empty_statistics}} = Server.init(refresh: true) - end - - test "refreshes when told to refresh" do - {:ok, _} = Server.init([]) - - assert_receive :refresh, 2_000 - end - end - - describe "handle_info/2" do - setup :state - - test "returns the original statistics when sent a :refresh message", %{ - state: %Server{statistics: statistics} = state - } do - assert {:noreply, %Server{statistics: ^statistics, task: task}} = Server.handle_info(:refresh, state) - - Task.await(task) - end - - test "launches a Statistics.fetch Task update when sent a :refresh message", %{state: state} do - assert {:noreply, %Server{task: %Task{} = task}} = Server.handle_info(:refresh, state) - - assert %Statistics{} = Task.await(task) - end - - test "stores successful Task in state", %{state: state} do - # so that `statistics` from Task will be different - insert(:block) - - assert {:noreply, %Server{task: %Task{ref: ref}} = refresh_state} = Server.handle_info(:refresh, state) - - assert_receive {^ref, %Statistics{} = message_statistics} = message - - assert {:noreply, %Server{statistics: ^message_statistics, task: nil}} = - Server.handle_info(message, refresh_state) - - refute message_statistics == state.statistics - end - - test "logs crashed Task", %{state: state} do - assert {:noreply, %Server{task: %Task{pid: pid, ref: ref}} = refresh_state} = Server.handle_info(:refresh, state) - - Process.exit(pid, :boom) - - assert_receive {:DOWN, ^ref, :process, ^pid, :boom} = message - - captured_log = - capture_log(fn -> - assert {:noreply, %Server{task: nil}} = Server.handle_info(message, refresh_state) - end) - - assert captured_log =~ "Explorer.Chain.Statistics.fetch failed and could not be cached: :boom" - end - end - - describe "handle_call/3" do - test "replies with statistics when sent a :fetch message" do - original = Statistics.fetch() - state = %Server{statistics: original} - - assert {:reply, ^original, ^state} = Server.handle_call(:fetch, self(), state) - end - end - - describe "terminate/2" do - setup :state - - test "cleans up in-progress tasks when terminated", %{state: state} do - assert {:noreply, %Server{task: %Task{pid: pid}} = refresh_state} = Server.handle_info(:refresh, state) - - second_ref = Process.monitor(pid) - - Server.terminate(:boom, refresh_state) - - assert_receive {:DOWN, ^second_ref, :process, ^pid, :shutdown} - end - end - - defp state(_) do - {:ok, state} = Server.init([]) - - %{state: state} - end -end diff --git a/apps/explorer/test/explorer/chain/statistics_test.exs b/apps/explorer/test/explorer/chain/statistics_test.exs deleted file mode 100644 index 45b7197920..0000000000 --- a/apps/explorer/test/explorer/chain/statistics_test.exs +++ /dev/null @@ -1,68 +0,0 @@ -defmodule Explorer.Chain.StatisticsTest do - use Explorer.DataCase - - alias Explorer.Chain.Statistics - alias Timex.Duration - - describe "fetch/0" do - test "returns -1 for the number when there are no blocks" do - assert %Statistics{number: -1} = Statistics.fetch() - end - - test "returns the highest block number when there is a block" do - insert(:block, number: 1) - - max_number = 100 - insert(:block, number: max_number) - - assert %Statistics{number: ^max_number} = Statistics.fetch() - end - - test "returns the latest block timestamp" do - time = DateTime.utc_now() - insert(:block, timestamp: time) - - statistics = Statistics.fetch() - - assert Timex.diff(statistics.timestamp, time, :seconds) == 0 - end - - test "returns the average time between blocks for the last 100 blocks" do - time = DateTime.utc_now() - - insert(:block, timestamp: Timex.shift(time, seconds: -1000)) - - for x <- 100..0 do - insert(:block, timestamp: Timex.shift(time, seconds: -5 * x)) - end - - assert %Statistics{ - average_time: %Duration{ - seconds: 5, - megaseconds: 0, - microseconds: 0 - } - } = Statistics.fetch() - end - - test "returns the last five blocks" do - insert_list(5, :block) - - statistics = Statistics.fetch() - - assert statistics.blocks |> Enum.count() == 4 - end - - test "returns the last five transactions with blocks" do - Enum.map(0..5, fn _ -> - :transaction - |> insert() - |> with_block() - end) - - statistics = Statistics.fetch() - - assert statistics.transactions |> Enum.count() == 5 - end - end -end diff --git a/apps/explorer_web/lib/explorer_web/controllers/chain_controller.ex b/apps/explorer_web/lib/explorer_web/controllers/chain_controller.ex index ab8ab89445..ffdc2bd1c4 100644 --- a/apps/explorer_web/lib/explorer_web/controllers/chain_controller.ex +++ b/apps/explorer_web/lib/explorer_web/controllers/chain_controller.ex @@ -1,30 +1,48 @@ defmodule ExplorerWeb.ChainController do use ExplorerWeb, :controller - alias Explorer.Chain.{Address, Block, Statistics, Transaction} + alias Explorer.{PagingOptions, Repo} + alias Explorer.Chain + alias Explorer.Chain.{Address, Block, Transaction} alias Explorer.ExchangeRates.Token alias Explorer.Market - alias ExplorerWeb.Chain def show(conn, _params) do - transaction_estimated_count = Explorer.Chain.transaction_estimated_count() - address_estimated_count = Explorer.Chain.address_estimated_count() + transaction_estimated_count = Chain.transaction_estimated_count() + address_estimated_count = Chain.address_estimated_count() + + transactions = + Chain.recent_collated_transactions( + necessity_by_association: %{ + block: :required, + from_address: :required, + to_address: :optional + }, + paging_options: %PagingOptions{page_size: 5} + ) + + blocks = + [paging_options: %PagingOptions{page_size: 4}] + |> Chain.list_blocks() + |> Repo.preload([:miner, :transactions]) render( conn, "show.html", address_estimated_count: address_estimated_count, - chain: Statistics.fetch(), + average_block_time: Chain.average_block_time(), + blocks: blocks, exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), market_history_data: Market.fetch_recent_history(30), - transaction_estimated_count: transaction_estimated_count + transaction_estimated_count: transaction_estimated_count, + transactions: transactions ) end def search(conn, %{"q" => query}) do query |> String.trim() - |> Chain.from_param() + |> ExplorerWeb.Chain.from_param() |> case do {:ok, item} -> redirect_search_results(conn, item) diff --git a/apps/explorer_web/lib/explorer_web/templates/chain/show.html.eex b/apps/explorer_web/lib/explorer_web/templates/chain/show.html.eex index 1d92f3f44c..ed19477357 100644 --- a/apps/explorer_web/lib/explorer_web/templates/chain/show.html.eex +++ b/apps/explorer_web/lib/explorer_web/templates/chain/show.html.eex @@ -28,7 +28,7 @@ <%= gettext "Average block time" %> - <%= Timex.format_duration(@chain.average_time, :humanized) %> + <%= Timex.format_duration(@average_block_time, :humanized) %>
@@ -57,7 +57,7 @@ <%= link(gettext("View All Blocks →"), to: block_path(ExplorerWeb.Endpoint, :index, Gettext.get_locale), class: "button button--secondary button--xsmall float-right") %>

<%= gettext "Blocks" %>

- <%= for block <- @chain.blocks do %> + <%= for block <- @blocks do %> <%= render ExplorerWeb.ChainView, "_block.html", locale: @locale, block: block %> <% end %>
@@ -74,7 +74,7 @@ <%= link(gettext("View All Transactions →"), to: transaction_path(ExplorerWeb.Endpoint, :index, Gettext.get_locale), class: "button button--secondary button--xsmall float-right") %>

<%= gettext "Transactions" %>

- <%= for transaction <- @chain.transactions do %> + <%= for transaction <- @transactions do %> <%= render ExplorerWeb.TransactionView, "_tile.html", locale: @locale, transaction: transaction %> <% end %> diff --git a/apps/explorer_web/test/explorer_web/controllers/chain_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/chain_controller_test.exs index 8b46530a01..98c0be9085 100644 --- a/apps/explorer_web/test/explorer_web/controllers/chain_controller_test.exs +++ b/apps/explorer_web/test/explorer_web/controllers/chain_controller_test.exs @@ -22,7 +22,7 @@ defmodule ExplorerWeb.ChainControllerTest do insert(:block, %{number: 23}) conn = get(conn, "/en") - assert(List.first(conn.assigns.chain.blocks).number == 23) + assert(List.first(conn.assigns.blocks).number == 23) end test "excludes all but the most recent five blocks", %{conn: conn} do @@ -30,7 +30,7 @@ defmodule ExplorerWeb.ChainControllerTest do insert_list(5, :block) conn = get(conn, "/en") - refute(Enum.member?(conn.assigns.chain.blocks, old_block)) + refute(Enum.member?(conn.assigns.blocks, old_block)) end test "only returns transactions with an associated block", %{conn: conn} do @@ -43,7 +43,7 @@ defmodule ExplorerWeb.ChainControllerTest do conn = get(conn, "/en") - transaction_hashes = Enum.map(conn.assigns.chain.transactions, fn transaction -> transaction.hash end) + transaction_hashes = Enum.map(conn.assigns.transactions, fn transaction -> transaction.hash end) assert(Enum.member?(transaction_hashes, associated.hash)) refute(Enum.member?(transaction_hashes, unassociated.hash)) @@ -57,7 +57,7 @@ defmodule ExplorerWeb.ChainControllerTest do conn = get(conn, "/en") - assert(List.first(conn.assigns.chain.transactions).hash == transaction.hash) + assert(List.first(conn.assigns.transactions).hash == transaction.hash) end test "returns market history data", %{conn: conn} do diff --git a/apps/explorer_web/test/explorer_web/features/pages/chain_page.ex b/apps/explorer_web/test/explorer_web/features/pages/chain_page.ex index 2e00e754a4..bd08da570c 100644 --- a/apps/explorer_web/test/explorer_web/features/pages/chain_page.ex +++ b/apps/explorer_web/test/explorer_web/features/pages/chain_page.ex @@ -5,7 +5,7 @@ defmodule ExplorerWeb.ChainPage do import Wallaby.Query, only: [css: 1, css: 2] - alias Explorer.Chain.{Block, InternalTransaction, Transaction} + alias Explorer.Chain.{Block, Transaction} def address_count(count) do css("[data-selector='address-count']", text: Integer.to_string(count)) @@ -23,7 +23,7 @@ defmodule ExplorerWeb.ChainPage do css("[data-selector='chain-block']", count: count) end - def contract_creation(%InternalTransaction{created_contract_address_hash: hash}) do + def contract_creation(%Transaction{created_contract_address_hash: hash}) do css("[data-test='contract-creation'] [data-address-hash='#{hash}']") end diff --git a/apps/explorer_web/test/explorer_web/features/viewing_chain_test.exs b/apps/explorer_web/test/explorer_web/features/viewing_chain_test.exs index 2b6bec110e..ce0753d66a 100644 --- a/apps/explorer_web/test/explorer_web/features/viewing_chain_test.exs +++ b/apps/explorer_web/test/explorer_web/features/viewing_chain_test.exs @@ -7,9 +7,9 @@ defmodule ExplorerWeb.ViewingChainTest do alias ExplorerWeb.{AddressPage, BlockPage, ChainPage, Notifier, TransactionPage} setup do - [oldest_block | _] = Enum.map(1..4, &insert(:block, number: &1)) + [oldest_block | _] = Enum.map(401..404, &insert(:block, number: &1)) - block = insert(:block, number: 5) + block = insert(:block, number: 405) [oldest_transaction | _] = 4 @@ -22,6 +22,7 @@ defmodule ExplorerWeb.ViewingChainTest do {:ok, %{ + block: block, last_shown_block: oldest_block, last_shown_transaction: oldest_transaction }} @@ -120,6 +121,7 @@ defmodule ExplorerWeb.ViewingChainTest do test "viewing new transactions via live update", %{ session: session, + block: block, last_shown_transaction: last_shown_transaction } do session @@ -129,7 +131,7 @@ defmodule ExplorerWeb.ViewingChainTest do transaction = :transaction |> insert() - |> with_block() + |> with_block(block) Notifier.handle_event({:chain_event, :transactions, [transaction.hash]}) @@ -139,11 +141,11 @@ defmodule ExplorerWeb.ViewingChainTest do |> refute_has(ChainPage.transaction(last_shown_transaction)) end - test "count of non-loaded transactions live update when batch overflow", %{session: session} do + test "count of non-loaded transactions live update when batch overflow", %{session: session, block: block} do transaction_hashes = 30 |> insert_list(:transaction) - |> with_block() + |> with_block(block) |> Enum.map(& &1.hash) session @@ -155,23 +157,23 @@ defmodule ExplorerWeb.ViewingChainTest do assert_has(session, ChainPage.non_loaded_transaction_count("30")) end - test "contract creation is shown for to_address", %{session: session} do + test "contract creation is shown for to_address", %{session: session, block: block} do contract_address = insert(:contract_address) transaction = :transaction |> insert(to_address: nil) |> with_contract_creation(contract_address) - |> with_block() + |> with_block(block) - internal_transaction = - :internal_transaction_create - |> insert(transaction: transaction, index: 0) - |> with_contract_creation(contract_address) + # internal_transaction = + # :internal_transaction_create + # |> insert(transaction: transaction, index: 0) + # |> with_contract_creation(contract_address) session |> ChainPage.visit_page() - |> assert_has(ChainPage.contract_creation(internal_transaction)) + |> assert_has(ChainPage.contract_creation(transaction)) end end end