From 39f19401c38e1eed23686ada77c7f14e9102143c Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 4 Jul 2019 12:11:52 +0300 Subject: [PATCH 1/7] add healthy block period checking query --- .../controllers/api/v1/health_controller.ex | 9 +++++++ apps/explorer/config/config.exs | 3 ++- apps/explorer/lib/explorer/chain.ex | 26 +++++++++++++++++++ apps/explorer/test/explorer/chain_test.exs | 18 +++++++++++++ 4 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 apps/block_scout_web/lib/block_scout_web/controllers/api/v1/health_controller.ex diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/health_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/health_controller.ex new file mode 100644 index 0000000000..abacc3c67b --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/health_controller.ex @@ -0,0 +1,9 @@ +defmodule BlockScoutWeb.API.V1.HealthController do + use BlockScoutWeb, :controller + + alias Explorer.Chain + + def last_block_status(conn, _) do + # Chain.last_block_status() + end +end diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index d834d23384..c63fe3e24c 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -14,7 +14,8 @@ config :explorer, System.get_env("ALLOWED_EVM_VERSIONS") || "homestead,tangerineWhistle,spuriousDragon,byzantium,constantinople,petersburg", include_uncles_in_average_block_time: - if(System.get_env("UNCLES_IN_AVERAGE_BLOCK_TIME") == "false", do: false, else: true) + if(System.get_env("UNCLES_IN_AVERAGE_BLOCK_TIME") == "false", do: false, else: true), + healthy_blocks_period: System.get_env("HEALTHY_BLOCKS_PERIOD") || :timer.minutes(5) config :explorer, Explorer.Counters.AverageBlockTime, enabled: true diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 62ff70afb8..d2d53046e2 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -1769,6 +1769,32 @@ defmodule Explorer.Chain do Repo.one!(query) end + @spec last_block_status() :: {:ok, non_neg_integer()} | {:error, non_neg_integer | nil} + def last_block_status do + query = + from(block in Block, + select: {block.number, block.timestamp}, + where: block.consensus == true, + order_by: [desc: block.number], + limit: 1 + ) + + case Repo.one(query) do + nil -> + {:error, :no_blocks} + + {_number, timestamp} -> + now = DateTime.utc_now() + last_block_period = DateTime.diff(now, timestamp, :millisecond) + + if last_block_period > Application.get_env(:explorer, :healthy_blocks_period) do + {:error, last_block_period} + else + {:ok, last_block_period} + end + end + end + @doc """ Calculates the ranges of missing consensus blocks in `range`. diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 91a4bff8db..2e28d9d269 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -50,6 +50,24 @@ defmodule Explorer.ChainTest do end end + describe "last_block_status/0" do + test "return no_blocks errors if db is empty" do + assert {:error, :no_blocks} = Chain.last_block_status() + end + + test "returns {:ok, last_block_period} if block is in healthy period" do + insert(:block, consensus: true) + + assert {:ok, _} = Chain.last_block_status() + end + + test "return {:ok, last_block_period} if block is not in healthy period" do + insert(:block, consensus: true, timestamp: Timex.shift(DateTime.utc_now(), hours: -50)) + + assert {:error, _} = Chain.last_block_status() + end + end + describe "address_to_logs/2" do test "fetches logs" do address = insert(:address) From 36b2eb001da8390ca0a2ad9b823e0a057e72acbb Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 4 Jul 2019 13:39:50 +0300 Subject: [PATCH 2/7] add health endpoint --- .../controllers/api/v1/health_controller.ex | 43 +++++++++++++++- .../lib/block_scout_web/router.ex | 2 + .../api/v1/health_controller_test.exs | 50 +++++++++++++++++++ apps/explorer/lib/explorer/chain.ex | 6 +-- apps/explorer/test/explorer/chain_test.exs | 4 +- 5 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 apps/block_scout_web/test/block_scout_web/controllers/api/v1/health_controller_test.exs diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/health_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/health_controller.ex index abacc3c67b..2abf592f3e 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/health_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/health_controller.ex @@ -4,6 +4,47 @@ defmodule BlockScoutWeb.API.V1.HealthController do alias Explorer.Chain def last_block_status(conn, _) do - # Chain.last_block_status() + status = Chain.last_block_status() + + case status do + {:ok, _, _} -> send_resp(conn, :ok, result(status)) + _ -> send_resp(conn, :internal_server_error, result(status)) + end + end + + def result({:ok, number, timestamp}) do + %{ + "healthy" => true, + "data" => %{ + "latest_block_number" => to_string(number), + "latest_block_inserted_at" => to_string(timestamp) + } + } + |> Jason.encode!() + end + + def result({:error, number, timestamp}) do + %{ + "healthy" => false, + "error_code" => 5001, + "error_title" => "blocks fetching is stuck", + "error_description" => + "There are no new blocks in the DB for the last 5 mins. Check the healthiness of Ethereum archive node or the Blockscout DB instance", + "data" => %{ + "latest_block_number" => to_string(number), + "latest_block_inserted_at" => to_string(timestamp) + } + } + |> Jason.encode!() + end + + def result({:error, :no_blocks}) do + %{ + "healthy" => false, + "error_code" => 5002, + "error_title" => "no blocks in db", + "error_description" => "There are no blocks in the DB" + } + |> Jason.encode!() end end 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 3a0b76b8ae..a535d81d00 100644 --- a/apps/block_scout_web/lib/block_scout_web/router.ex +++ b/apps/block_scout_web/lib/block_scout_web/router.ex @@ -23,6 +23,8 @@ defmodule BlockScoutWeb.Router do get("/supply", SupplyController, :supply) + get("/health/last_block_status", HealthController, :last_block_status) + resources("/decompiled_smart_contract", DecompiledSmartContractController, only: [:create]) resources("/verified_smart_contracts", VerifiedSmartContractController, only: [:create]) end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v1/health_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v1/health_controller_test.exs new file mode 100644 index 0000000000..8acab7cc52 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v1/health_controller_test.exs @@ -0,0 +1,50 @@ +defmodule BlockScoutWeb.API.V1.HealthControllerTest do + use BlockScoutWeb.ConnCase + + describe "GET last_block_status/0" do + test "returns error when there are no blocks in db", %{conn: conn} do + request = get(conn, api_v1_health_path(conn, :last_block_status)) + + assert request.status == 500 + + assert request.resp_body == + "{\"error_code\":5002,\"error_description\":\"There are no blocks in the DB\",\"error_title\":\"no blocks in db\",\"healthy\":false}" + end + + test "returns error when last block is stale", %{conn: conn} do + insert(:block, consensus: true, timestamp: Timex.shift(DateTime.utc_now(), hours: -50)) + + request = get(conn, api_v1_health_path(conn, :last_block_status)) + + assert request.status == 500 + + assert %{ + "healthy" => false, + "error_code" => 5001, + "error_title" => "blocks fetching is stuck", + "error_description" => + "There are no new blocks in the DB for the last 5 mins. Check the healthiness of Ethereum archive node or the Blockscout DB instance", + "data" => %{ + "latest_block_number" => _, + "latest_block_inserted_at" => _ + } + } = Poison.decode!(request.resp_body) + end + + test "returns ok when last block is not stale", %{conn: conn} do + insert(:block, consensus: true, timestamp: DateTime.utc_now()) + + request = get(conn, api_v1_health_path(conn, :last_block_status)) + + assert request.status == 200 + + assert %{ + "healthy" => true, + "data" => %{ + "latest_block_number" => _, + "latest_block_inserted_at" => _ + } + } = Poison.decode!(request.resp_body) + end + end +end diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index d2d53046e2..1b73d6edc7 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -1783,14 +1783,14 @@ defmodule Explorer.Chain do nil -> {:error, :no_blocks} - {_number, timestamp} -> + {number, timestamp} -> now = DateTime.utc_now() last_block_period = DateTime.diff(now, timestamp, :millisecond) if last_block_period > Application.get_env(:explorer, :healthy_blocks_period) do - {:error, last_block_period} + {:error, number, timestamp} else - {:ok, last_block_period} + {:ok, number, timestamp} end end end diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 2e28d9d269..cfb8b5ab14 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -58,13 +58,13 @@ defmodule Explorer.ChainTest do test "returns {:ok, last_block_period} if block is in healthy period" do insert(:block, consensus: true) - assert {:ok, _} = Chain.last_block_status() + assert {:ok, _, _} = Chain.last_block_status() end test "return {:ok, last_block_period} if block is not in healthy period" do insert(:block, consensus: true, timestamp: Timex.shift(DateTime.utc_now(), hours: -50)) - assert {:error, _} = Chain.last_block_status() + assert {:error, _, _} = Chain.last_block_status() end end From fd752eb92a57a797c83533dc91a645df4e6946d3 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 4 Jul 2019 13:59:39 +0300 Subject: [PATCH 3/7] fix dialyzer --- .../controllers/api/v1/health_controller.ex | 23 ++++++++++--------- apps/explorer/lib/explorer/chain.ex | 1 - 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/health_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/health_controller.ex index 2abf592f3e..d935d15012 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/health_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/health_controller.ex @@ -8,7 +8,8 @@ defmodule BlockScoutWeb.API.V1.HealthController do case status do {:ok, _, _} -> send_resp(conn, :ok, result(status)) - _ -> send_resp(conn, :internal_server_error, result(status)) + {:error, _} -> send_resp(conn, :internal_server_error, result(status)) + {:error, _, _} -> send_resp(conn, :internal_server_error, result(status)) end end @@ -23,6 +24,16 @@ defmodule BlockScoutWeb.API.V1.HealthController do |> Jason.encode!() end + def result({:error, :no_blocks}) do + %{ + "healthy" => false, + "error_code" => 5002, + "error_title" => "no blocks in db", + "error_description" => "There are no blocks in the DB" + } + |> Jason.encode!() + end + def result({:error, number, timestamp}) do %{ "healthy" => false, @@ -37,14 +48,4 @@ defmodule BlockScoutWeb.API.V1.HealthController do } |> Jason.encode!() end - - def result({:error, :no_blocks}) do - %{ - "healthy" => false, - "error_code" => 5002, - "error_title" => "no blocks in db", - "error_description" => "There are no blocks in the DB" - } - |> Jason.encode!() - end end diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 1b73d6edc7..fcad694a58 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -1769,7 +1769,6 @@ defmodule Explorer.Chain do Repo.one!(query) end - @spec last_block_status() :: {:ok, non_neg_integer()} | {:error, non_neg_integer | nil} def last_block_status do query = from(block in Block, From a5f44f292e2d59f24d80bec97b9a29652503e84b Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 4 Jul 2019 14:01:17 +0300 Subject: [PATCH 4/7] add CHANGELOG entry --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ede02ddea1..8f52fd6dbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### Features +[#2294](https://github.com/poanetwork/blockscout/pull/2294) - add healthy block period checking endpoint + ### Fixes ### Chore From 072ce483e6e49fc5112e168005b31fdde2096b85 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 4 Jul 2019 16:33:17 +0300 Subject: [PATCH 5/7] fix CR issues --- .../controllers/api/v1/health_controller.ex | 18 ++++++++---------- .../lib/block_scout_web/router.ex | 2 +- .../api/v1/health_controller_test.exs | 6 +++--- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/health_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/health_controller.ex index d935d15012..957dc797be 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/health_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/health_controller.ex @@ -3,17 +3,15 @@ defmodule BlockScoutWeb.API.V1.HealthController do alias Explorer.Chain - def last_block_status(conn, _) do - status = Chain.last_block_status() - - case status do - {:ok, _, _} -> send_resp(conn, :ok, result(status)) - {:error, _} -> send_resp(conn, :internal_server_error, result(status)) - {:error, _, _} -> send_resp(conn, :internal_server_error, result(status)) + def health(conn, _) do + with {:ok, number, timestamp} <- Chain.last_block_status() do + send_resp(conn, :ok, result(number, timestamp)) + else + status -> send_resp(conn, :internal_server_error, error(status)) end end - def result({:ok, number, timestamp}) do + def result(number, timestamp) do %{ "healthy" => true, "data" => %{ @@ -24,7 +22,7 @@ defmodule BlockScoutWeb.API.V1.HealthController do |> Jason.encode!() end - def result({:error, :no_blocks}) do + def error({:error, :no_blocks}) do %{ "healthy" => false, "error_code" => 5002, @@ -34,7 +32,7 @@ defmodule BlockScoutWeb.API.V1.HealthController do |> Jason.encode!() end - def result({:error, number, timestamp}) do + def error({:error, number, timestamp}) do %{ "healthy" => false, "error_code" => 5001, 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 a535d81d00..d13dc9c43e 100644 --- a/apps/block_scout_web/lib/block_scout_web/router.ex +++ b/apps/block_scout_web/lib/block_scout_web/router.ex @@ -23,7 +23,7 @@ defmodule BlockScoutWeb.Router do get("/supply", SupplyController, :supply) - get("/health/last_block_status", HealthController, :last_block_status) + get("/health", HealthController, :health) resources("/decompiled_smart_contract", DecompiledSmartContractController, only: [:create]) resources("/verified_smart_contracts", VerifiedSmartContractController, only: [:create]) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v1/health_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v1/health_controller_test.exs index 8acab7cc52..41735f685a 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v1/health_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v1/health_controller_test.exs @@ -3,7 +3,7 @@ defmodule BlockScoutWeb.API.V1.HealthControllerTest do describe "GET last_block_status/0" do test "returns error when there are no blocks in db", %{conn: conn} do - request = get(conn, api_v1_health_path(conn, :last_block_status)) + request = get(conn, api_v1_health_path(conn, :health)) assert request.status == 500 @@ -14,7 +14,7 @@ defmodule BlockScoutWeb.API.V1.HealthControllerTest do test "returns error when last block is stale", %{conn: conn} do insert(:block, consensus: true, timestamp: Timex.shift(DateTime.utc_now(), hours: -50)) - request = get(conn, api_v1_health_path(conn, :last_block_status)) + request = get(conn, api_v1_health_path(conn, :health)) assert request.status == 500 @@ -34,7 +34,7 @@ defmodule BlockScoutWeb.API.V1.HealthControllerTest do test "returns ok when last block is not stale", %{conn: conn} do insert(:block, consensus: true, timestamp: DateTime.utc_now()) - request = get(conn, api_v1_health_path(conn, :last_block_status)) + request = get(conn, api_v1_health_path(conn, :health)) assert request.status == 200 From b88e468634e50d752d6357896a9b754a1c3960ca Mon Sep 17 00:00:00 2001 From: maxgrapps Date: Fri, 5 Jul 2019 15:56:16 +0300 Subject: [PATCH 6/7] footer grid fix for md resolution --- .../block_scout_web/templates/layout/_footer.html.eex | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/_footer.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_footer.html.eex index 7188f283a1..9d84836a5a 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/_footer.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_footer.html.eex @@ -13,7 +13,7 @@ <% col_size = if Enum.empty?(other_explorers), do: 3, else: 2 %>
-
+
-