diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/block_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/block_controller.ex
index 302d6641be..b90cf9c82c 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/block_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/block_controller.ex
@@ -22,6 +22,20 @@ defmodule BlockScoutWeb.BlockController do
redirect(conn, to: block_transaction_path(conn, :index, hash_or_number))
end
+ def reorg(conn, params) do
+ Keyword.merge(
+ [
+ necessity_by_association: %{
+ :transactions => :optional,
+ [miner: :names] => :optional
+ },
+ block_type: "Reorg"
+ ],
+ paging_options(params)
+ )
+ |> handle_render(conn, params)
+ end
+
def uncle(conn, params) do
Keyword.merge(
[
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/block_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/block_transaction_controller.ex
index b5249eb679..979b74fc27 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/block_transaction_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/block_transaction_controller.ex
@@ -11,7 +11,8 @@ defmodule BlockScoutWeb.BlockTransactionController do
param_block_hash_or_number_to_block(formatted_block_hash_or_number,
necessity_by_association: %{
[miner: :names] => :required,
- :uncles => :optional
+ :uncles => :optional,
+ :nephews => :optional
}
) do
block_transaction_count = Chain.block_to_transaction_count(block)
diff --git a/apps/block_scout_web/lib/block_scout_web/router.ex b/apps/block_scout_web/lib/block_scout_web/router.ex
index e2ba8d7cd3..6c9b8eb768 100644
--- a/apps/block_scout_web/lib/block_scout_web/router.ex
+++ b/apps/block_scout_web/lib/block_scout_web/router.ex
@@ -46,6 +46,8 @@ defmodule BlockScoutWeb.Router do
resources("/transactions", BlockTransactionController, only: [:index], as: :transaction)
end
+ get("/reorgs", BlockController, :reorg, as: :reorg)
+
get("/uncles", BlockController, :uncle, as: :uncle)
resources("/pending_transactions", PendingTransactionController, only: [:index])
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex
index 10548510ff..345cc75377 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex
@@ -5,15 +5,15 @@
- <%= gettext("%{type} Details", type: (if uncle?(@block), do: "Uncle", else: "Block")) %>
+ <%= gettext("%{block_type} Details", block_type: block_type(@block)) %>
- <%= if uncle?(@block) do %>
- <%= gettext("Uncle Height:") %>
- <%= link(@block, to: block_path(BlockScoutWeb.Endpoint, :show, @block.number)) %>
- <% else %>
+ <%= if block_type(@block) == "Block" do %>
<%= gettext("Block Height: %{height}", height: @block.number) %>
+ <% else %>
+ <%= gettext("%{block_type} Height:", block_type: block_type(@block)) %>
+ <%= link(@block, to: block_path(BlockScoutWeb.Endpoint, :show, @block.number)) %>
<% end %>
@@ -57,7 +57,7 @@
- <%= if not uncle?(@block) do %>
+ <%= if block_type(@block) == "Block" do %>
- <%= gettext "Total Difficulty" %>
diff --git a/apps/block_scout_web/lib/block_scout_web/views/block_view.ex b/apps/block_scout_web/lib/block_scout_web/views/block_view.ex
index 06299ae0a3..b5308da4db 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/block_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/block_view.ex
@@ -20,6 +20,10 @@ defmodule BlockScoutWeb.BlockView do
"#{average} #{unit_text}"
end
+ def block_type(%Block{consensus: false, nephews: []}), do: "Reorg"
+ def block_type(%Block{consensus: false}), do: "Uncle"
+ def block_type(_block), do: "Block"
+
@doc """
Work-around for spec issue in `Cldr.Unit.to_string!/1`
"""
@@ -42,6 +46,4 @@ defmodule BlockScoutWeb.BlockView do
def formatted_timestamp(%Block{timestamp: timestamp}) do
Timex.format!(timestamp, "%b-%d-%Y %H:%M:%S %p %Z", :strftime)
end
-
- def uncle?(%Block{consensus: consensus}), do: !consensus
end
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/block_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/block_controller_test.exs
index 1a6e840355..d67765fe84 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/block_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/block_controller_test.exs
@@ -109,6 +109,35 @@ defmodule BlockScoutWeb.BlockControllerTest do
end
end
+ describe "GET reorgs/2" do
+ test "returns all reorgs", %{conn: conn} do
+ reorg_hashes =
+ 4
+ |> insert_list(:block, consensus: false)
+ |> Enum.map(& &1.hash)
+ conn = get(conn, reorg_path(conn, :reorg))
+
+ assert Enum.map(conn.assigns.blocks, & &1.hash) == Enum.reverse(reorg_hashes)
+ assert conn.assigns.block_type == "Reorg"
+ end
+
+ test "does not include blocks or uncles", %{conn: conn} do
+ reorg_hashes =
+ 4
+ |> insert_list(:block, consensus: false)
+ |> Enum.map(& &1.hash)
+
+ insert(:block)
+ uncle = insert(:block, consensus: false)
+ insert(:block_second_degree_relation, uncle_hash: uncle.hash)
+
+ conn = get(conn, reorg_path(conn, :reorg))
+
+ assert Enum.map(conn.assigns.blocks, & &1.hash) == Enum.reverse(reorg_hashes)
+ assert conn.assigns.block_type == "Reorg"
+ end
+ end
+
describe "GET uncle/2" do
test "returns all uncles", %{conn: conn} do
uncle_hashes =
diff --git a/apps/block_scout_web/test/block_scout_web/features/pages/block_list_page.ex b/apps/block_scout_web/test/block_scout_web/features/pages/block_list_page.ex
index 2f91d76332..d9dbd447b6 100644
--- a/apps/block_scout_web/test/block_scout_web/features/pages/block_list_page.ex
+++ b/apps/block_scout_web/test/block_scout_web/features/pages/block_list_page.ex
@@ -11,6 +11,10 @@ defmodule BlockScoutWeb.BlockListPage do
visit(session, "/blocks")
end
+ def visit_reorgs_page(session) do
+ visit(session, "/reorgs")
+ end
+
def visit_uncles_page(session) do
visit(session, "/uncles")
end
diff --git a/apps/block_scout_web/test/block_scout_web/features/viewing_blocks_test.exs b/apps/block_scout_web/test/block_scout_web/features/viewing_blocks_test.exs
index 19e78983c8..c325e14fbd 100644
--- a/apps/block_scout_web/test/block_scout_web/features/viewing_blocks_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/features/viewing_blocks_test.exs
@@ -150,6 +150,17 @@ defmodule BlockScoutWeb.ViewingBlocksTest do
end
end
+ describe "viewing reorg blocks list" do
+ test "lists uncle blocks", %{session: session} do
+ [reorg | _] = insert_list(10, :block, consensus: false)
+
+ session
+ |> BlockListPage.visit_reorgs_page()
+ |> assert_has(BlockListPage.block(reorg))
+ |> assert_has(BlockListPage.blocks(10))
+ end
+ end
+
describe "viewing uncle blocks list" do
setup do
uncles =
diff --git a/apps/block_scout_web/test/block_scout_web/views/block_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/block_view_test.exs
index b08c8bed5c..8c4c4c7c91 100644
--- a/apps/block_scout_web/test/block_scout_web/views/block_view_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/views/block_view_test.exs
@@ -18,26 +18,33 @@ defmodule BlockScoutWeb.BlockViewTest do
end
end
- describe "formatted_timestamp/1" do
- test "returns a formatted timestamp string for a block" do
- block = insert(:block)
+ describe "block_type/1" do
+ test "returns Block" do
+ block = insert(:block, nephews: [])
- assert Timex.format!(block.timestamp, "%b-%d-%Y %H:%M:%S %p %Z", :strftime) ==
- BlockView.formatted_timestamp(block)
+ assert BlockView.block_type(block) == "Block"
end
- end
- describe "uncle?/1" do
- test "returns true for an uncle block" do
+ test "returns Reorg" do
+ reorg = insert(:block, consensus: false, nephews: [])
+
+ assert BlockView.block_type(reorg) == "Reorg"
+ end
+
+ test "returns Uncle" do
uncle = insert(:block, consensus: false)
+ insert(:block_second_degree_relation, uncle_hash: uncle.hash)
- assert BlockView.uncle?(uncle)
+ assert BlockView.block_type(uncle) == "Uncle"
end
+ end
- test "returns false for a block" do
+ describe "formatted_timestamp/1" do
+ test "returns a formatted timestamp string for a block" do
block = insert(:block)
- refute BlockView.uncle?(block)
+ assert Timex.format!(block.timestamp, "%b-%d-%Y %H:%M:%S %p %Z", :strftime) ==
+ BlockView.formatted_timestamp(block)
end
end
end
diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex
index a1df296a0d..1dd2548299 100644
--- a/apps/explorer/lib/explorer/chain.ex
+++ b/apps/explorer/lib/explorer/chain.ex
@@ -869,11 +869,11 @@ defmodule Explorer.Chain do
block_type = Keyword.get(options, :block_type, "Block")
Block
- |> join_associations(necessity_by_association)
- |> page_blocks(paging_options)
|> Block.block_type_filter(block_type)
+ |> page_blocks(paging_options)
|> limit(^paging_options.page_size)
|> order_by(desc: :number)
+ |> join_associations(necessity_by_association)
|> Repo.all()
end
diff --git a/apps/explorer/lib/explorer/chain/block.ex b/apps/explorer/lib/explorer/chain/block.ex
index cfa0b82918..4b9a4f96bf 100644
--- a/apps/explorer/lib/explorer/chain/block.ex
+++ b/apps/explorer/lib/explorer/chain/block.ex
@@ -98,10 +98,15 @@ defmodule Explorer.Chain.Block do
end
- @doc """
- Adds to the given block's query a `where` with conditions to filter by the type of block;
- `Uncle`, `Reorg`, or `Block`. `Uncle`'s are already filtered based on requiring nephews.
- """
- def block_type_filter(query, "Block"), do: where(query, [block], block.consensus == true)
- def block_type_filter(query, "Uncle"), do: query
+ @doc """
+ Adds to the given block's query a `where` with conditions to filter by the type of block;
+ `Uncle`, `Reorg`, or `Block`. `Uncle`'s are already filtered based on requiring nephews.
+ """
+ def block_type_filter(query, "Block"), do: where(query, [block], block.consensus == true)
+ def block_type_filter(query, "Reorg") do
+ query
+ |> join(:left, [block], uncles in assoc(block, :nephew_relations))
+ |> where([block, uncles], block.consensus == false and is_nil(uncles.uncle_hash))
+ end
+ def block_type_filter(query, "Uncle"), do: query
end