From a74042674261477fe6b88589f9faa7907dee7f0d Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Wed, 13 Dec 2023 18:50:48 +0600 Subject: [PATCH] Isolate throttable error count by request method --- CHANGELOG.md | 1 + .../ethereum_jsonrpc/request_coordinator.ex | 29 ++++++++++++------- .../request_coordinator_test.exs | 29 ++++++++++++------- 3 files changed, 38 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 719f013806..0457d876f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- [#8997](https://github.com/blockscout/blockscout/pull/8997) - Isolate throttable error count by request method - [#8972](https://github.com/blockscout/blockscout/pull/8972) - BENS integration - [#8957](https://github.com/blockscout/blockscout/pull/8957) - Add Tx Interpreter Service integration diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/request_coordinator.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/request_coordinator.ex index a80db36feb..77ee7d3906 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/request_coordinator.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/request_coordinator.ex @@ -58,7 +58,7 @@ defmodule EthereumJSONRPC.RequestCoordinator do alias EthereumJSONRPC.{RollingWindow, Tracer, Transport} - @error_key :throttleable_error_count + @error_key_base "throttleable_error_count" @throttle_key :throttle_requests_count @doc """ @@ -73,7 +73,9 @@ defmodule EthereumJSONRPC.RequestCoordinator do @spec perform(Transport.batch_request(), Transport.t(), Transport.options(), non_neg_integer()) :: {:ok, Transport.batch_response()} | {:error, term()} def perform(request, transport, transport_options, throttle_timeout) do - sleep_time = sleep_time() + request_method = request_method(request) + + sleep_time = sleep_time(request_method) if sleep_time <= throttle_timeout do :timer.sleep(sleep_time) @@ -85,7 +87,7 @@ defmodule EthereumJSONRPC.RequestCoordinator do trace_request(request, fn -> request |> transport.json_rpc(transport_options) - |> handle_transport_response() + |> handle_transport_response(request_method) end) :error -> @@ -110,19 +112,24 @@ defmodule EthereumJSONRPC.RequestCoordinator do defp trace_request(_, fun), do: fun.() - defp handle_transport_response({:error, {error_type, _}} = error) when error_type in [:bad_gateway, :bad_response] do - RollingWindow.inc(table(), @error_key) + defp request_method([request | _]), do: request_method(request) + defp request_method(%{method: method}), do: method + defp request_method(_), do: nil + + defp handle_transport_response({:error, {error_type, _}} = error, method) + when error_type in [:bad_gateway, :bad_response] do + RollingWindow.inc(table(), method_error_key(method)) inc_throttle_table() error end - defp handle_transport_response({:error, :timeout} = error) do - RollingWindow.inc(table(), @error_key) + defp handle_transport_response({:error, :timeout} = error, method) do + RollingWindow.inc(table(), method_error_key(method)) inc_throttle_table() error end - defp handle_transport_response(response) do + defp handle_transport_response(response, _method) do inc_throttle_table() response end @@ -154,14 +161,16 @@ defmodule EthereumJSONRPC.RequestCoordinator do end end - defp sleep_time do - wait_coefficient = RollingWindow.count(table(), @error_key) + defp sleep_time(request_method) do + wait_coefficient = RollingWindow.count(table(), method_error_key(request_method)) jitter = :rand.uniform(config!(:max_jitter)) wait_per_timeout = config!(:wait_per_timeout) wait_coefficient * (wait_per_timeout + jitter) end + defp method_error_key(method), do: :"#{@error_key_base}_#{method}" + defp table do :rolling_window_opts |> config!() diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/request_coordinator_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/request_coordinator_test.exs index efda05ab86..0eaa6c1f31 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/request_coordinator_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/request_coordinator_test.exs @@ -26,28 +26,35 @@ defmodule EthereumJSONRPC.RequestCoordinatorTest do describe "perform/4" do test "forwards result whenever a request doesn't timeout", %{timeout_table: timeout_table} do expect(EthereumJSONRPC.Mox, :json_rpc, fn _, _ -> {:ok, %{}} end) - assert RollingWindow.count(timeout_table, :throttleable_error_count) == 0 - assert {:ok, %{}} == RequestCoordinator.perform(%{}, EthereumJSONRPC.Mox, [], :timer.minutes(60)) - assert RollingWindow.count(timeout_table, :throttleable_error_count) == 0 + assert RollingWindow.count(timeout_table, :throttleable_error_count_eth_call) == 0 + + assert {:ok, %{}} == + RequestCoordinator.perform(%{method: "eth_call"}, EthereumJSONRPC.Mox, [], :timer.minutes(60)) + + assert RollingWindow.count(timeout_table, :throttleable_error_count_eth_call) == 0 end test "increments counter on certain errors", %{timeout_table: timeout_table} do - expect(EthereumJSONRPC.Mox, :json_rpc, fn :timeout, _ -> {:error, :timeout} end) - expect(EthereumJSONRPC.Mox, :json_rpc, fn :bad_gateway, _ -> {:error, {:bad_gateway, "message"}} end) + expect(EthereumJSONRPC.Mox, :json_rpc, fn %{method: "timeout"}, _ -> {:error, :timeout} end) + expect(EthereumJSONRPC.Mox, :json_rpc, fn %{method: "bad_gateway"}, _ -> {:error, {:bad_gateway, "message"}} end) + + assert {:error, :timeout} == + RequestCoordinator.perform(%{method: "timeout"}, EthereumJSONRPC.Mox, [], :timer.minutes(60)) - assert {:error, :timeout} == RequestCoordinator.perform(:timeout, EthereumJSONRPC.Mox, [], :timer.minutes(60)) - assert RollingWindow.count(timeout_table, :throttleable_error_count) == 1 + assert RollingWindow.count(timeout_table, :throttleable_error_count_timeout) == 1 + assert RollingWindow.count(timeout_table, :throttleable_error_count_bad_gateway) == 0 assert {:error, {:bad_gateway, "message"}} == - RequestCoordinator.perform(:bad_gateway, EthereumJSONRPC.Mox, [], :timer.minutes(60)) + RequestCoordinator.perform(%{method: "bad_gateway"}, EthereumJSONRPC.Mox, [], :timer.minutes(60)) - assert RollingWindow.count(timeout_table, :throttleable_error_count) == 2 + assert RollingWindow.count(timeout_table, :throttleable_error_count_timeout) == 1 + assert RollingWindow.count(timeout_table, :throttleable_error_count_bad_gateway) == 1 end test "returns timeout error if sleep time will exceed max timeout", %{timeout_table: timeout_table} do expect(EthereumJSONRPC.Mox, :json_rpc, 0, fn _, _ -> :ok end) - RollingWindow.inc(timeout_table, :throttleable_error_count) - assert {:error, :timeout} == RequestCoordinator.perform(%{}, EthereumJSONRPC.Mox, [], 1) + RollingWindow.inc(timeout_table, :throttleable_error_count_eth_call) + assert {:error, :timeout} == RequestCoordinator.perform(%{method: "eth_call"}, EthereumJSONRPC.Mox, [], 1) end test "increments throttle_table even when not an error", %{throttle_table: throttle_table} do