From ab6111b4667966ce112e887595a0a891937c2db0 Mon Sep 17 00:00:00 2001 From: goodsoft Date: Sun, 17 Mar 2019 20:37:01 +0200 Subject: [PATCH] Chunk JSON-RPC batches in case connection times out There exists code to ensure long-running batch requests are split into smaller batches in case we receive 504 (Gateway Timeout) errorfrom loadbalancer. This PR adds handling of batches in the same manner in case there either is no balancer, or its timeouts are configured larger than indexer's, and thus the HTTP client library itself times out. --- .../lib/ethereum_jsonrpc/http.ex | 3 + .../test/ethereum_jsonrpc/http/mox_test.exs | 92 +++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex index 95247198eb..ded7304364 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex @@ -68,6 +68,9 @@ defmodule EthereumJSONRPC.HTTP do chunked_json_rpc(tail, options, [decoded_body | decoded_response_bodies]) end + {:error, :timeout} -> + rechunk_json_rpc(chunks, options, :timeout, decoded_response_bodies) + {:error, _} = error -> error end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/http/mox_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/http/mox_test.exs index ea11fe9187..43c0622a40 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/http/mox_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/http/mox_test.exs @@ -189,6 +189,98 @@ defmodule EthereumJSONRPC.HTTP.MoxTest do assert MapSet.equal?(response_block_number_set, block_number_set) end + + @tag :no_geth + # Regression test for https://github.com/poanetwork/blockscout/issues/418 + test "transparently splits batch payloads that would trigger a request timeout", %{ + json_rpc_named_arguments: json_rpc_named_arguments + } do + block_numbers = [862_272, 862_273, 862_274, 862_275, 862_276, 862_277, 862_278, 862_279, 862_280, 862_281] + + if json_rpc_named_arguments[:transport_options][:http] == EthereumJSONRPC.HTTP.Mox do + EthereumJSONRPC.HTTP.Mox + |> expect(:json_rpc, fn _url, _json, _options -> + {:error, :timeout} + end) + |> expect(:json_rpc, fn _url, json, _options -> + json_binary = IO.iodata_to_binary(json) + + refute json_binary =~ "0xD2849" + assert json_binary =~ "0xD2844" + + body = + 0..4 + |> Enum.map(fn id -> + %{ + jsonrpc: "2.0", + id: id, + result: [ + %{ + "trace" => [ + %{ + "type" => "create", + "action" => %{"from" => "0x", "gas" => "0x0", "init" => "0x", "value" => "0x0"}, + "traceAddress" => "0x", + "result" => %{"address" => "0x", "code" => "0x", "gasUsed" => "0x0"} + } + ], + "transactionHash" => "0x221aaf59f7a05702f0f53744b4fdb5f74e3c6fdade7324fda342cc1ebc73e01c" + } + ] + } + end) + |> Jason.encode!() + + {:ok, %{body: body, status_code: 200}} + end) + |> expect(:json_rpc, fn _url, json, _options -> + json_binary = IO.iodata_to_binary(json) + + refute json_binary =~ "0xD2844" + assert json_binary =~ "0xD2845" + assert json_binary =~ "0xD2849" + + body = + 5..9 + |> Enum.map(fn id -> + %{ + jsonrpc: "2.0", + id: id, + result: [ + %{ + "trace" => [ + %{ + "type" => "create", + "action" => %{"from" => "0x", "gas" => "0x0", "init" => "0x", "value" => "0x0"}, + "traceAddress" => "0x", + "result" => %{"address" => "0x", "code" => "0x", "gasUsed" => "0x0"} + } + ], + "transactionHash" => "0x221aaf59f7a05702f0f53744b4fdb5f74e3c6fdade7324fda342cc1ebc73e01c" + } + ] + } + end) + |> Jason.encode!() + + {:ok, %{body: body, status_code: 200}} + end) + end + + assert {:ok, responses} = + EthereumJSONRPC.fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments) + + assert Enum.count(responses) == Enum.count(block_numbers) + + block_number_set = MapSet.new(block_numbers) + + response_block_number_set = + Enum.into(responses, MapSet.new(), fn %{block_number: block_number} -> + block_number + end) + + assert MapSet.equal?(response_block_number_set, block_number_set) + end end defp assert_payload_too_large(payload, json_rpc_named_arguments) do