From 673c5f5d3f9a8c7ecfdb184301faf6654fcdc03d Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Mon, 22 May 2023 21:37:53 +0400 Subject: [PATCH] Handle empty id in json rpc responses --- CHANGELOG.md | 2 ++ apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex | 33 +++++++++++++++++-- .../lib/ethereum_jsonrpc/blocks.ex | 1 + .../lib/ethereum_jsonrpc/fetched_balances.ex | 1 + .../ethereum_jsonrpc/fetched_beneficiaries.ex | 1 + .../lib/ethereum_jsonrpc/fetched_codes.ex | 1 + .../lib/ethereum_jsonrpc/geth.ex | 1 + .../lib/ethereum_jsonrpc/http.ex | 11 ++++--- .../lib/ethereum_jsonrpc/receipts.ex | 1 + .../trace_replay_block_transactions.ex | 2 ++ .../test/ethereum_jsonrpc_test.exs | 2 +- 11 files changed, 48 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b590f8e884a..97fe897103fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### Features +- [#9072](https://github.com/blockscout/blockscout/pull/9072) - Add tracing by block logic for geth +- [#7532](https://github.com/blockscout/blockscout/pull/7532) - Handle empty id in json rpc responses - [#6721](https://github.com/blockscout/blockscout/pull/6721) - Implement fetching internal transactions from callTracer - [#5561](https://github.com/blockscout/blockscout/pull/5561), [#6523](https://github.com/blockscout/blockscout/pull/6523) - Improve working with contracts implementations - [#6401](https://github.com/blockscout/blockscout/pull/6401) - Add Sol2Uml contract visualization diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex index 9a9258a742ad..8a7106b595b3 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex @@ -25,6 +25,8 @@ defmodule EthereumJSONRPC do documentation for `EthereumJSONRPC.RequestCoordinator`. """ + require Logger + alias EthereumJSONRPC.{ Block, Blocks, @@ -380,6 +382,29 @@ defmodule EthereumJSONRPC do |> Enum.into(%{}, fn {params, id} -> {id, params} end) end + @doc """ + Assigns not matched ids between requests and responses to responses with incorrect ids + """ + def sanitize_responses(responses, id_to_params) do + responses + |> Enum.reduce( + {[], Map.keys(id_to_params) -- Enum.map(responses, & &1.id)}, + fn + %{id: nil} = res, {result_res, [id | rest]} -> + Logger.error( + "Empty id in response: #{inspect(res)}, stacktrace: #{inspect(Process.info(self(), :current_stacktrace))}" + ) + + {[%{res | id: id} | result_res], rest} + + res, {result_res, non_matched} -> + {[res | result_res], non_matched} + end + ) + |> elem(0) + |> Enum.reverse() + end + @doc """ 1. POSTs JSON `payload` to `url` 2. Decodes the response @@ -404,7 +429,7 @@ defmodule EthereumJSONRPC do @doc """ Converts `t:quantity/0` to `t:non_neg_integer/0`. """ - @spec quantity_to_integer(quantity) :: non_neg_integer() | :error + @spec quantity_to_integer(quantity) :: non_neg_integer() | nil def quantity_to_integer("0x" <> hexadecimal_digits) do String.to_integer(hexadecimal_digits, 16) end @@ -414,10 +439,12 @@ defmodule EthereumJSONRPC do def quantity_to_integer(string) when is_binary(string) do case Integer.parse(string) do {integer, ""} -> integer - _ -> :error + _ -> nil end end + def quantity_to_integer(_), do: nil + @doc """ Converts `t:non_neg_integer/0` to `t:quantity/0` """ @@ -489,7 +516,7 @@ defmodule EthereumJSONRPC do """ def timestamp_to_datetime(timestamp) do case quantity_to_integer(timestamp) do - :error -> + nil -> nil quantity -> diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex index dc1740a4aaa5..4d157b317749 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex @@ -32,6 +32,7 @@ defmodule EthereumJSONRPC.Blocks do def from_responses(responses, id_to_params) when is_list(responses) and is_map(id_to_params) do %{errors: errors, blocks: blocks} = responses + |> EthereumJSONRPC.sanitize_responses(id_to_params) |> Enum.map(&Block.from_response(&1, id_to_params)) |> Enum.reduce(%{errors: [], blocks: []}, fn {:ok, block}, %{blocks: blocks} = acc -> diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_balances.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_balances.ex index dc629c720a39..da769b5e5a2d 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_balances.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_balances.ex @@ -19,6 +19,7 @@ defmodule EthereumJSONRPC.FetchedBalances do """ def from_responses(responses, id_to_params) do responses + |> EthereumJSONRPC.sanitize_responses(id_to_params) |> Enum.map(&FetchedBalance.from_response(&1, id_to_params)) |> Enum.reduce( %__MODULE__{}, diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_beneficiaries.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_beneficiaries.ex index 4e55e6e2a249..60e65665d967 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_beneficiaries.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_beneficiaries.ex @@ -77,6 +77,7 @@ defmodule EthereumJSONRPC.FetchedBeneficiaries do """ def from_responses(responses, id_to_params) when is_list(responses) and is_map(id_to_params) do responses + |> EthereumJSONRPC.sanitize_responses(id_to_params) |> Enum.map(&response_to_params_set(&1, id_to_params)) |> Enum.reduce( %EthereumJSONRPC.FetchedBeneficiaries{}, diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_codes.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_codes.ex index 8369285194a3..257037990931 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_codes.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_codes.ex @@ -34,6 +34,7 @@ defmodule EthereumJSONRPC.FetchedCodes do """ def from_responses(responses, id_to_params) do responses + |> EthereumJSONRPC.sanitize_responses(id_to_params) |> Enum.map(&FetchedCode.from_response(&1, id_to_params)) |> Enum.reduce( %__MODULE__{}, diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex index 599dbb32e262..f2a9bc404c78 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex @@ -136,6 +136,7 @@ defmodule EthereumJSONRPC.Geth do ) when is_list(responses) and is_map(id_to_params) do responses + |> EthereumJSONRPC.sanitize_responses(id_to_params) |> Enum.map(&debug_trace_transaction_response_to_internal_transactions_params(&1, id_to_params)) |> reduce_internal_transactions_params() end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex index 5f01093dae0f..226145ca64d4 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex @@ -159,15 +159,18 @@ defmodule EthereumJSONRPC.HTTP do standardized = %{jsonrpc: jsonrpc, id: id} - case unstandardized do - %{"result" => _, "error" => _} -> + case {id, unstandardized} do + {_id, %{"result" => _, "error" => _}} -> raise ArgumentError, "result and error keys are mutually exclusive in JSONRPC 2.0 response objects, but got #{inspect(unstandardized)}" - %{"result" => result} -> + {nil, %{"result" => error}} -> + Map.put(standardized, :error, standardize_error(error)) + + {_id, %{"result" => result}} -> Map.put(standardized, :result, result) - %{"error" => error} -> + {_id, %{"error" => error}} -> Map.put(standardized, :error, standardize_error(error)) end end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex index b3d177411e42..dc1478ecb0a4 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex @@ -265,6 +265,7 @@ defmodule EthereumJSONRPC.Receipts do defp reduce_responses(responses, id_to_transaction_params) when is_list(responses) and is_map(id_to_transaction_params) do responses + |> EthereumJSONRPC.sanitize_responses(id_to_transaction_params) |> Stream.map(&response_to_receipt(&1, id_to_transaction_params)) |> Enum.reduce({:ok, []}, &reduce_receipt(&1, &2)) end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/trace_replay_block_transactions.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/trace_replay_block_transactions.ex index 49b395b86b04..8fc6adfead5d 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/trace_replay_block_transactions.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/trace_replay_block_transactions.ex @@ -66,6 +66,7 @@ defmodule EthereumJSONRPC.TraceReplayBlockTransactions do defp trace_replay_block_transactions_responses_to_traces(responses, id_to_params) when is_list(responses) and is_map(id_to_params) do responses + |> EthereumJSONRPC.sanitize_responses(id_to_params) |> Enum.map(&trace_replay_block_transactions_response_to_traces(&1, id_to_params)) |> Enum.reduce( {:ok, []}, @@ -158,6 +159,7 @@ defmodule EthereumJSONRPC.TraceReplayBlockTransactions do defp trace_replay_transaction_responses_to_first_trace(responses, id_to_params) when is_list(responses) and is_map(id_to_params) do responses + |> EthereumJSONRPC.sanitize_responses(id_to_params) |> Enum.map(&trace_replay_transaction_response_to_first_trace(&1, id_to_params)) |> Enum.reduce( {:ok, []}, diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs index b0a2ec4b3136..fdf034ee5f61 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs @@ -973,7 +973,7 @@ defmodule EthereumJSONRPCSyncTest do params_list: [ %{ address_hash: hash, - block_number: :error, + block_number: nil, value: expected_fetched_balance } ]