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