From a8e2e127b54e9b1e9a99cdf89d93736a8335716d Mon Sep 17 00:00:00 2001 From: nikitosing <32202610+nikitosing@users.noreply.github.com> Date: Fri, 7 Jun 2024 13:05:13 +0300 Subject: [PATCH] feat: Batch read methods requests (#10192) * feat: Batch read methods requests * Fix tests * Process review comments --- .../api/v2/smart_contract_controller_test.exs | 18 ++- .../lib/explorer/smart_contract/reader.ex | 105 +++++++++++++----- 2 files changed, 90 insertions(+), 33 deletions(-) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs index 99f30a2085..c0e19f0b9c 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs @@ -1677,23 +1677,33 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do target_contract = insert(:smart_contract, abi: abi) - blockchain_eth_call_mock() + address_hash = to_string(target_contract.address_hash) expect( EthereumJSONRPC.Mox, :json_rpc, fn [ %{ - id: id, + id: id_1, method: "eth_call", - params: [%{to: _address_hash, from: "0xBb36c792B9B45Aaf8b848A1392B0d6559202729E"}, _] + params: [%{to: ^address_hash, from: "0xBb36c792B9B45Aaf8b848A1392B0d6559202729E", data: "0x2e64cec1"}, _] + }, + %{ + id: id_2, + method: "eth_call", + params: [%{to: ^address_hash, from: "0xBb36c792B9B45Aaf8b848A1392B0d6559202729E", data: "0xab470f05"}, _] } ], _opts -> {:ok, [ %{ - id: id, + id: id_2, + jsonrpc: "2.0", + result: "0x000000000000000000000000fffffffffffffffffffffffffffffffffffffffe" + }, + %{ + id: id_1, jsonrpc: "2.0", result: "0x0000000000000000000000000000000000000000000000000000000000000020fe6a43fa23a0269092cbf97cb908e1d5a49a18fd6942baf2467fb5b221e39ab200000000000000000000000000000000000000000000000000000000000003e8fe6a43fa23a0269092cbf97cb908e1d5a49a18fd6942baf2467fb5b221e39ab2000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000bb36c792b9b45aaf8b848a1392b0d6559202729e000000000000000000000000bb36c792b9b45aaf8b848a1392b0d6559202729e000000000000000000000000000000000000000000000000000000000001e0f30000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000bb36c792b9b45aaf8b848a1392b0d6559202729e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000003307830000000000000000000000000000000000000000000000000000000000030783030313132323333000000000000000000000000000000000000000000003078303031313232333331323300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c3078303030303132333132330000000000000000000000000000000000000000000000000000000000000000bb36c792b9b45aaf8b848a1392b0d6559202729e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000003307830000000000000000000000000000000000000000000000000000000000030783030313132323333000000000000000000000000000000000000000000003078303031313232333331323300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c3078303030303132333132330000000000000000000000000000000000000000000000000000000000000000bb36c792b9b45aaf8b848a1392b0d6559202729e000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000003307830000000000000000000000000000000000000000000000000000000000030783030313132323333000000000000000000000000000000000000000000003078303031313232333331323300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c3078303030303132333132330000000000000000000000000000000000000000" diff --git a/apps/explorer/lib/explorer/smart_contract/reader.ex b/apps/explorer/lib/explorer/smart_contract/reader.ex index 2b7d5e7cc6..804e27ff69 100644 --- a/apps/explorer/lib/explorer/smart_contract/reader.ex +++ b/apps/explorer/lib/explorer/smart_contract/reader.ex @@ -291,9 +291,7 @@ defmodule Explorer.SmartContract.Reader do abi_with_method_id |> Enum.filter(&Helper.queriable_method?(&1)) - |> Enum.map( - &fetch_current_value_from_blockchain(&1, abi_with_method_id, contract_address_hash, false, options, from) - ) + |> fetch_current_values_from_blockchain(abi_with_method_id, contract_address_hash, false, options, from) end def read_only_functions_from_abi_with_sender(_, _, _, _), do: [] @@ -356,39 +354,88 @@ defmodule Explorer.SmartContract.Reader do "tuple[#{tuple_types}]" end - def fetch_current_value_from_blockchain( - function, + @spec fetch_current_values_from_blockchain( + any(), + [%{optional(binary()) => any()}], + Explorer.Chain.Hash.t(), + boolean(), + keyword(), + nil | binary() + ) :: [SmartContract.function_description()] + def fetch_current_values_from_blockchain( + functions, abi, contract_address_hash, leave_error_as_map, options, from \\ nil ) do - case function do - %{"inputs" => []} -> - method_id = function["method_id"] - args = function["inputs"] - - %{output: outputs, names: names} = - query_function_with_names( - contract_address_hash, - %{method_id: method_id, args: args}, - :regular, - from, - abi, - leave_error_as_map, - options - ) - - function - |> Map.replace!("outputs", outputs) - |> Map.put("abi_outputs", Map.get(function, "outputs", [])) - |> Map.put("names", names) + initial_methods_id_order = Enum.map(functions, &Map.get(&1, "method_id")) + + %{to_be_fetched: to_be_fetched, method_id_to_outputs: method_id_to_outputs, unchanged: unchanged} = + Enum.reduce( + functions, + %{to_be_fetched: %{}, method_id_to_outputs: %{}, unchanged: %{}}, + fn function, + %{ + to_be_fetched: to_be_fetched, + unchanged: unchanged, + method_id_to_outputs: method_id_to_outputs + } -> + case function do + %{"inputs" => []} -> + [%ABI.FunctionSelector{returns: returns, method_id: _method_id}] = ABI.parse_specification([function]) + + outputs = extract_outputs(returns) + + %{ + to_be_fetched: Map.put(to_be_fetched, function["method_id"], function), + unchanged: unchanged, + method_id_to_outputs: Map.put(method_id_to_outputs, function["method_id"], {outputs, function}) + } + + _ -> + %{ + to_be_fetched: to_be_fetched, + unchanged: + Map.put( + unchanged, + function["method_id"], + Map.put(function, "abi_outputs", Map.get(function, "outputs", [])) + ), + method_id_to_outputs: method_id_to_outputs + } + end + end + ) - _ -> - function - |> Map.put("abi_outputs", Map.get(function, "outputs", [])) - end + methods = to_be_fetched |> Enum.map(fn {method_id, _function} -> {method_id, []} end) |> Enum.into(%{}) + + res = + contract_address_hash + |> query_verified_contract(methods, from, leave_error_as_map, abi, options) + + method_id_to_abi_with_fetched_value = + res + |> Enum.map(fn {method_id, _result} -> + {outputs, function} = method_id_to_outputs[method_id] + + names = outputs_to_list(function["outputs"]) + + outputs = link_outputs_and_values(res, outputs, method_id) + function = to_be_fetched[method_id] + + {method_id, + function + |> Map.replace!("outputs", outputs) + |> Map.put("abi_outputs", Map.get(function, "outputs", [])) + |> Map.put("names", names)} + end) + |> Enum.into(%{}) + + Enum.map(initial_methods_id_order, fn method_id -> + unchanged[method_id] || method_id_to_abi_with_fetched_value[method_id] + end) end @doc """