From a2aa6ecc49f7ecdbd73f061c102fb85752880195 Mon Sep 17 00:00:00 2001 From: bchamagne <74045243+bchamagne@users.noreply.github.com> Date: Mon, 2 Jan 2023 17:35:59 +0100 Subject: [PATCH] Prevent HTTP/WS request when node is bootstraping (#788) * Return a 503 when node is bootstraping * add test for the WS * use an alias * rename file --- lib/archethic/beacon_chain/slot_timer.ex | 19 ++++++++-------- lib/archethic/bootstrap.ex | 4 ++++ lib/archethic/oracle_chain/scheduler.ex | 22 +++++++++---------- lib/archethic/reward/scheduler.ex | 19 +++++++--------- lib/archethic/self_repair/sync.ex | 3 ++- .../shared_secrets/node_renewal_scheduler.ex | 18 +++++++-------- lib/archethic_web/channels/user_socket.ex | 11 +++++++++- lib/archethic_web/controllers/up_controler.ex | 19 ---------------- .../controllers/up_controller.ex | 12 ++++++++++ lib/archethic_web/endpoint.ex | 22 +++++++++++++++++++ lib/archethic_web/supervisor.ex | 1 - .../controllers/api/up_controller_test.exs | 21 ++++++++++++++++++ test/archethic_web/graphql_schema_test.exs | 6 +++++ test/support/conn_case.ex | 3 +++ 14 files changed, 115 insertions(+), 65 deletions(-) delete mode 100644 lib/archethic_web/controllers/up_controler.ex create mode 100644 lib/archethic_web/controllers/up_controller.ex create mode 100644 test/archethic_web/controllers/api/up_controller_test.exs diff --git a/lib/archethic/beacon_chain/slot_timer.ex b/lib/archethic/beacon_chain/slot_timer.ex index aa21c63a5..c362faa6b 100644 --- a/lib/archethic/beacon_chain/slot_timer.ex +++ b/lib/archethic/beacon_chain/slot_timer.ex @@ -9,6 +9,7 @@ defmodule Archethic.BeaconChain.SlotTimer do alias Archethic.BeaconChain alias Archethic.BeaconChain.SubsetRegistry alias Archethic.BeaconChain.SummaryTimer + alias Archethic.Bootstrap alias Archethic.DB @@ -87,18 +88,16 @@ defmodule Archethic.BeaconChain.SlotTimer do interval = Keyword.get(opts, :interval) :ets.insert(@slot_timer_ets, {:interval, interval}) - case :persistent_term.get(:archethic_up, nil) do - nil -> - Logger.info("Slot Timer: Waiting for Node to complete Bootstrap.") - - Archethic.PubSub.register_to_node_up() - {:ok, %{interval: interval}} + if Bootstrap.done?() do + Logger.info("Slot Timer: Starting...") + next_time = next_slot(DateTime.utc_now()) - :up -> - Logger.info("Slot Timer: Starting...") - next_time = next_slot(DateTime.utc_now()) + {:ok, %{interval: interval, timer: schedule_new_slot(interval), next_time: next_time}} + else + Logger.info("Slot Timer: Waiting for Node to complete Bootstrap.") - {:ok, %{interval: interval, timer: schedule_new_slot(interval), next_time: next_time}} + Archethic.PubSub.register_to_node_up() + {:ok, %{interval: interval}} end end diff --git a/lib/archethic/bootstrap.ex b/lib/archethic/bootstrap.ex index 755401b0d..7eb13ba09 100644 --- a/lib/archethic/bootstrap.ex +++ b/lib/archethic/bootstrap.ex @@ -63,6 +63,10 @@ defmodule Archethic.Bootstrap do ]) end + def done?() do + :persistent_term.get(:archethic_up, nil) == :up + end + @doc """ Start the bootstrap workflow. diff --git a/lib/archethic/oracle_chain/scheduler.ex b/lib/archethic/oracle_chain/scheduler.ex index 23b35794e..b30a2f57f 100644 --- a/lib/archethic/oracle_chain/scheduler.ex +++ b/lib/archethic/oracle_chain/scheduler.ex @@ -3,6 +3,7 @@ defmodule Archethic.OracleChain.Scheduler do Manage the scheduling of the oracle transactions """ + alias Archethic.Bootstrap alias Archethic.Crypto alias Archethic.Election @@ -60,20 +61,17 @@ defmodule Archethic.OracleChain.Scheduler do |> Map.put(:polling_interval, polling_interval) |> Map.put(:summary_interval, summary_interval) - case :persistent_term.get(:archethic_up, nil) do - nil -> - # node still bootstrapping , wait for it to finish Bootstrap - Logger.info(" Oracle Scheduler: Waiting for Node to complete Bootstrap. ") - - PubSub.register_to_node_up() + if Bootstrap.done?() do + # when node is already bootstrapped, - handles scheduler crash + {state, new_state_data, events} = start_scheduler(state_data) + {:ok, state, new_state_data, events} + else + # node still bootstrapping , wait for it to finish Bootstrap + Logger.info(" Oracle Scheduler: Waiting for Node to complete Bootstrap. ") - {:ok, :idle, state_data} + PubSub.register_to_node_up() - # wait for node UP - :up -> - # when node is already bootstrapped, - handles scheduler crash - {state, new_state_data, events} = start_scheduler(state_data) - {:ok, state, new_state_data, events} + {:ok, :idle, state_data} end end diff --git a/lib/archethic/reward/scheduler.ex b/lib/archethic/reward/scheduler.ex index 9f0706584..f64eae9f1 100644 --- a/lib/archethic/reward/scheduler.ex +++ b/lib/archethic/reward/scheduler.ex @@ -5,6 +5,7 @@ defmodule Archethic.Reward.Scheduler do @vsn Mix.Project.config()[:version] alias Archethic.{ + Bootstrap, Crypto, PubSub, DB, @@ -29,18 +30,14 @@ defmodule Archethic.Reward.Scheduler do # Set trap_exit globally for the process Process.flag(:trap_exit, true) - case :persistent_term.get(:archethic_up, nil) do - nil -> - Logger.info(" Reward Scheduler: Waiting for Node to complete Bootstrap. ") - - PubSub.register_to_node_up() - {:ok, :idle, state_data} - - # wait for node up + if Bootstrap.done?() do + {state, new_state_data, events} = start_scheduler(state_data) + {:ok, state, new_state_data, events} + else + Logger.info(" Reward Scheduler: Waiting for Node to complete Bootstrap. ") - :up -> - {state, new_state_data, events} = start_scheduler(state_data) - {:ok, state, new_state_data, events} + PubSub.register_to_node_up() + {:ok, :idle, state_data} end end diff --git a/lib/archethic/self_repair/sync.ex b/lib/archethic/self_repair/sync.ex index 5ddca9a18..594eff73c 100644 --- a/lib/archethic/self_repair/sync.ex +++ b/lib/archethic/self_repair/sync.ex @@ -5,6 +5,7 @@ defmodule Archethic.SelfRepair.Sync do alias Archethic.BeaconChain.Subset.P2PSampling alias Archethic.BeaconChain.Summary alias Archethic.BeaconChain.SummaryAggregate + alias Archethic.Bootstrap alias Archethic.Crypto @@ -270,7 +271,7 @@ defmodule Archethic.SelfRepair.Sync do new_available_nodes = P2P.authorized_and_available_nodes(availability_update) - if :persistent_term.get(:archethic_up, nil) == :up do + if Bootstrap.done?() do SelfRepair.start_notifier( previous_available_nodes, new_available_nodes, diff --git a/lib/archethic/shared_secrets/node_renewal_scheduler.ex b/lib/archethic/shared_secrets/node_renewal_scheduler.ex index f8a912381..91d77c0d1 100644 --- a/lib/archethic/shared_secrets/node_renewal_scheduler.ex +++ b/lib/archethic/shared_secrets/node_renewal_scheduler.ex @@ -11,6 +11,7 @@ defmodule Archethic.SharedSecrets.NodeRenewalScheduler do alias Archethic + alias Archethic.Bootstrap alias Archethic.Election alias Archethic.Crypto @@ -52,17 +53,14 @@ defmodule Archethic.SharedSecrets.NodeRenewalScheduler do # Set trap_exit globally for the process Process.flag(:trap_exit, true) - case :persistent_term.get(:archethic_up, nil) do - nil -> - Logger.info("Node Renewal Scheduler: Waiting for node to complete Bootstrap. ") - - PubSub.register_to_node_up() - {:ok, :idle, state_data} + if Bootstrap.done?() do + {state, new_state_data, events} = start_scheduler(state_data) + {:ok, state, new_state_data, events} + else + Logger.info("Node Renewal Scheduler: Waiting for node to complete Bootstrap. ") - # wait for node ups - :up -> - {state, new_state_data, events} = start_scheduler(state_data) - {:ok, state, new_state_data, events} + PubSub.register_to_node_up() + {:ok, :idle, state_data} end end diff --git a/lib/archethic_web/channels/user_socket.ex b/lib/archethic_web/channels/user_socket.ex index 4c1e1da88..96751a9c6 100644 --- a/lib/archethic_web/channels/user_socket.ex +++ b/lib/archethic_web/channels/user_socket.ex @@ -6,6 +6,10 @@ defmodule ArchethicWeb.UserSocket do use Absinthe.Phoenix.Socket, schema: ArchethicWeb.GraphQLSchema + alias Archethic.Bootstrap + + require Logger + ## Channels # channel "room:*", ArchethicWeb.RoomChannel @@ -21,7 +25,12 @@ defmodule ArchethicWeb.UserSocket do # See `Phoenix.Token` documentation for examples in # performing token verification on connect. def connect(_params, socket, _connect_info) do - {:ok, socket} + if Bootstrap.done?() do + {:ok, socket} + else + Logger.debug("Received a websocket connect but node is bootstraping") + :error + end end # Socket id's are topics that allow you to identify all sockets for a given user: diff --git a/lib/archethic_web/controllers/up_controler.ex b/lib/archethic_web/controllers/up_controler.ex deleted file mode 100644 index 6a920c82b..000000000 --- a/lib/archethic_web/controllers/up_controler.ex +++ /dev/null @@ -1,19 +0,0 @@ -defmodule ArchethicWeb.UpController do - @moduledoc false - - use ArchethicWeb, :controller - - @doc """ - Respond with 200 when node is ready otherwise with 503. - Used only to indicate that the first testnet node is bootrapped. - """ - def up(conn, _) do - :up = :persistent_term.get(:archethic_up) - rescue - _ -> - resp(conn, 503, "wait") - else - _ -> - resp(conn, 200, "up") - end -end diff --git a/lib/archethic_web/controllers/up_controller.ex b/lib/archethic_web/controllers/up_controller.ex new file mode 100644 index 000000000..f56ca892b --- /dev/null +++ b/lib/archethic_web/controllers/up_controller.ex @@ -0,0 +1,12 @@ +defmodule ArchethicWeb.UpController do + @moduledoc false + + use ArchethicWeb, :controller + + @doc """ + The logic to respond 503 when node is not bootstraped is moved in a plug + """ + def up(conn, _) do + resp(conn, 200, "up") + end +end diff --git a/lib/archethic_web/endpoint.ex b/lib/archethic_web/endpoint.ex index 0ab33c2ad..2cb46eb8c 100644 --- a/lib/archethic_web/endpoint.ex +++ b/lib/archethic_web/endpoint.ex @@ -4,6 +4,12 @@ defmodule ArchethicWeb.Endpoint do use Phoenix.Endpoint, otp_app: :archethic use Absinthe.Phoenix.Endpoint + alias Archethic.Bootstrap + + require Logger + + plug(:archethic_up) + # The session will be stored in the cookie and signed, # this means its contents can be read but not tampered with. # Set :encryption_salt if you would also like to encrypt it. @@ -51,4 +57,20 @@ defmodule ArchethicWeb.Endpoint do plug(Plug.Session, @session_options) plug(CORSPlug, origin: "*") plug(ArchethicWeb.RouterDispatch) + + # don't serve anything before the node is bootstraped + # + # ps: this handle only HTTP(S) requests + # for WS, see archethic_web/channels/user_socket.ex + defp archethic_up(conn, _opts) do + if Bootstrap.done?() do + conn + else + Logger.debug("Received a web request but node is bootstraping") + + conn + |> send_resp(503, "") + |> halt() + end + end end diff --git a/lib/archethic_web/supervisor.ex b/lib/archethic_web/supervisor.ex index 75f3e4f9a..bd9402af6 100644 --- a/lib/archethic_web/supervisor.ex +++ b/lib/archethic_web/supervisor.ex @@ -27,7 +27,6 @@ defmodule ArchethicWeb.Supervisor do TransactionCache, TopTransactionsCache, {Phoenix.PubSub, [name: ArchethicWeb.PubSub, adapter: Phoenix.PubSub.PG2]}, - # Start the endpoint when the application starts Endpoint, {Absinthe.Subscription, Endpoint}, TransactionSubscriber diff --git a/test/archethic_web/controllers/api/up_controller_test.exs b/test/archethic_web/controllers/api/up_controller_test.exs new file mode 100644 index 000000000..804c84a4c --- /dev/null +++ b/test/archethic_web/controllers/api/up_controller_test.exs @@ -0,0 +1,21 @@ +defmodule ArchethicWeb.UpControllerTest do + @moduledoc false + use ArchethicCase + use ArchethicWeb.ConnCase + + test "should return 503 when bootstrap is not over", %{conn: conn} do + :persistent_term.put(:archethic_up, nil) + + conn = get(conn, "/up") + + assert "" = response(conn, 503) + end + + test "should return 200 when bootstrap is over", %{conn: conn} do + :persistent_term.put(:archethic_up, :up) + + conn = get(conn, "/up") + + assert "up" = response(conn, 200) + end +end diff --git a/test/archethic_web/graphql_schema_test.exs b/test/archethic_web/graphql_schema_test.exs index 8d9e63c03..9611335ea 100644 --- a/test/archethic_web/graphql_schema_test.exs +++ b/test/archethic_web/graphql_schema_test.exs @@ -545,4 +545,10 @@ defmodule ArchethicWeb.GraphQLSchemaTest do assert recv_addr == Base.encode16(addr) end end + + test "should fail to connect if node is bootstraping" do + :persistent_term.put(:archethic_up, nil) + + assert_raise MatchError, fn -> get_socket() end + end end diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index b81e87e5c..f1bf435ab 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -35,6 +35,9 @@ defmodule ArchethicWeb.ConnCase do end setup _tags do + # mark the node as bootstraped + :persistent_term.put(:archethic_up, :up) + start_supervised!(FaucetRateLimiter) {:ok, conn: ConnTest.build_conn()} end