From 60cd238adf322085fe787494694fafcc3c7ed245 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 16 Jul 2019 11:15:41 +0300 Subject: [PATCH 01/24] add logs pagination stub --- apps/explorer/lib/explorer/etherscan/logs.ex | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/apps/explorer/lib/explorer/etherscan/logs.ex b/apps/explorer/lib/explorer/etherscan/logs.ex index 9e9d70ae02..921ae1bcdb 100644 --- a/apps/explorer/lib/explorer/etherscan/logs.ex +++ b/apps/explorer/lib/explorer/etherscan/logs.ex @@ -38,6 +38,8 @@ defmodule Explorer.Etherscan.Logs do :type ] + @default_paging_options %{block_number: nil, transaction_index: nil} + @doc """ Gets a list of logs that meet the criteria in a given filter map. @@ -68,7 +70,9 @@ defmodule Explorer.Etherscan.Logs do """ @spec list_logs(map()) :: [map()] - def list_logs(%{address_hash: address_hash} = filter) when not is_nil(address_hash) do + def list_logs(filter, paging_options \\ @default_paging_options) + + def list_logs(%{address_hash: address_hash} = filter, paging_options) when not is_nil(address_hash) do prepared_filter = Map.merge(@base_filter, filter) logs_query = where_topic_match(Log, prepared_filter) @@ -134,14 +138,16 @@ defmodule Explorer.Etherscan.Logs do ) end - Repo.all(query_with_consensus) + query_with_consensus + |> page_logs(paging_options) + |> Repo.all() end # Since address_hash was not present, we know that a # topic filter has been applied, so we use a different # query that is optimized for a logs filter over an # address_hash - def list_logs(filter) do + def list_logs(filter, paging_options) do prepared_filter = Map.merge(@base_filter, filter) logs_query = where_topic_match(Log, prepared_filter) @@ -182,7 +188,9 @@ defmodule Explorer.Etherscan.Logs do select_merge: map(log, ^@log_fields) ) - Repo.all(query_with_block_transaction_data) + query_with_block_transaction_data + |> page_logs(paging_options) + |> Repo.all() end @topics [ @@ -231,4 +239,6 @@ defmodule Explorer.Etherscan.Logs do end defp where_multiple_topics_match(query, _, _, _), do: query + + defp page_logs(query, _paging_options), do: query end From 3ede158963bed9f4dd7001c8fd5e650ed95843a6 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 16 Jul 2019 11:16:30 +0300 Subject: [PATCH 02/24] move eth rpc logic to separate module from controller --- .../controllers/api/rpc/eth_controller.ex | 323 +----------------- .../controllers/api_docs_controller.ex | 4 +- apps/explorer/lib/explorer/eth_rpc.ex | 322 +++++++++++++++++ 3 files changed, 328 insertions(+), 321 deletions(-) create mode 100644 apps/explorer/lib/explorer/eth_rpc.ex diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/eth_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/eth_controller.ex index bb386940b1..26ffd5e47f 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/eth_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/eth_controller.ex @@ -1,38 +1,10 @@ defmodule BlockScoutWeb.API.RPC.EthController do use BlockScoutWeb, :controller - alias Ecto.Type, as: EctoType - alias Explorer.{Chain, Repo} - alias Explorer.Chain.{Block, Data, Hash, Hash.Address, Wei} - alias Explorer.Etherscan.Logs - - @methods %{ - "eth_getBalance" => %{ - action: :eth_get_balance, - notes: """ - the `earliest` parameter will not work as expected currently, because genesis block balances - are not currently imported - """ - }, - "eth_getLogs" => %{ - action: :eth_get_logs, - notes: """ - Will never return more than 1000 log entries. - """ - } - } - - @index_to_word %{ - 0 => "first", - 1 => "second", - 2 => "third", - 3 => "fourth" - } - - def methods, do: @methods + alias Explorer.EthRPC def eth_request(%{body_params: %{"_json" => requests}} = conn, _) when is_list(requests) do - responses = responses(requests) + responses = EthRPC.responses(requests) conn |> put_status(200) @@ -40,7 +12,7 @@ defmodule BlockScoutWeb.API.RPC.EthController do end def eth_request(%{body_params: %{"_json" => request}} = conn, _) do - [response] = responses([request]) + [response] = EthRPC.responses([request]) conn |> put_status(200) @@ -59,297 +31,10 @@ defmodule BlockScoutWeb.API.RPC.EthController do _ -> request end - [response] = responses([decoded_request]) + [response] = EthRPC.responses([decoded_request]) conn |> put_status(200) |> render("response.json", %{response: response}) end - - def eth_get_balance(address_param, block_param \\ nil) do - with {:address, {:ok, address}} <- {:address, Chain.string_to_address_hash(address_param)}, - {:block, {:ok, block}} <- {:block, block_param(block_param)}, - {:balance, {:ok, balance}} <- {:balance, Chain.get_balance_as_of_block(address, block)} do - {:ok, Wei.hex_format(balance)} - else - {:address, :error} -> - {:error, "Query parameter 'address' is invalid"} - - {:block, :error} -> - {:error, "Query parameter 'block' is invalid"} - - {:balance, {:error, :not_found}} -> - {:error, "Balance not found"} - end - end - - def eth_get_logs(filter_options) do - with {:ok, address_or_topic_params} <- address_or_topic_params(filter_options), - {:ok, from_block_param, to_block_param} <- logs_blocks_filter(filter_options), - {:ok, from_block} <- cast_block(from_block_param), - {:ok, to_block} <- cast_block(to_block_param) do - filter = - address_or_topic_params - |> Map.put(:from_block, from_block) - |> Map.put(:to_block, to_block) - |> Map.put(:allow_non_consensus, true) - - {:ok, filter |> Logs.list_logs() |> Enum.map(&render_log/1)} - else - {:error, message} when is_bitstring(message) -> - {:error, message} - - {:error, :empty} -> - {:ok, []} - - _ -> - {:error, "Something went wrong."} - end - end - - defp render_log(log) do - topics = - Enum.reject( - [log.first_topic, log.second_topic, log.third_topic, log.fourth_topic], - &is_nil/1 - ) - - %{ - "address" => to_string(log.address_hash), - "blockHash" => to_string(log.block_hash), - "blockNumber" => Integer.to_string(log.block_number, 16), - "data" => to_string(log.data), - "logIndex" => Integer.to_string(log.index, 16), - "removed" => log.block_consensus == false, - "topics" => topics, - "transactionHash" => to_string(log.transaction_hash), - "transactionIndex" => log.transaction_index, - "transactionLogIndex" => log.index, - "type" => "mined" - } - end - - defp cast_block("0x" <> hexadecimal_digits = input) do - case Integer.parse(hexadecimal_digits, 16) do - {integer, ""} -> {:ok, integer} - _ -> {:error, input <> " is not a valid block number"} - end - end - - defp cast_block(integer) when is_integer(integer), do: {:ok, integer} - defp cast_block(_), do: {:error, "invalid block number"} - - defp address_or_topic_params(filter_options) do - address_param = Map.get(filter_options, "address") - topics_param = Map.get(filter_options, "topics") - - with {:ok, address} <- validate_address(address_param), - {:ok, topics} <- validate_topics(topics_param) do - address_and_topics(address, topics) - end - end - - defp address_and_topics(nil, nil), do: {:error, "Must supply one of address and topics"} - defp address_and_topics(address, nil), do: {:ok, %{address_hash: address}} - defp address_and_topics(nil, topics), do: {:ok, topics} - defp address_and_topics(address, topics), do: {:ok, Map.put(topics, :address_hash, address)} - - defp validate_address(nil), do: {:ok, nil} - - defp validate_address(address) do - case Address.cast(address) do - {:ok, address} -> {:ok, address} - :error -> {:error, "invalid address"} - end - end - - defp validate_topics(nil), do: {:ok, nil} - defp validate_topics([]), do: [] - - defp validate_topics(topics) when is_list(topics) do - topics - |> Stream.with_index() - |> Enum.reduce({:ok, %{}}, fn {topic, index}, {:ok, acc} -> - case cast_topics(topic) do - {:ok, data} -> - with_filter = Map.put(acc, String.to_existing_atom("#{@index_to_word[index]}_topic"), data) - - {:ok, add_operator(with_filter, index)} - - :error -> - {:error, "invalid topics"} - end - end) - end - - defp add_operator(filters, 0), do: filters - - defp add_operator(filters, index) do - Map.put(filters, String.to_existing_atom("topic#{index - 1}_#{index}_opr"), "and") - end - - defp cast_topics(topics) when is_list(topics) do - case EctoType.cast({:array, Data}, topics) do - {:ok, data} -> {:ok, Enum.map(data, &to_string/1)} - :error -> :error - end - end - - defp cast_topics(topic) do - case Data.cast(topic) do - {:ok, data} -> {:ok, to_string(data)} - :error -> :error - end - end - - defp responses(requests) do - Enum.map(requests, fn request -> - with {:id, {:ok, id}} <- {:id, Map.fetch(request, "id")}, - {:request, {:ok, result}} <- {:request, do_eth_request(request)} do - format_success(result, id) - else - {:id, :error} -> format_error("id is a required field", 0) - {:request, {:error, message}} -> format_error(message, Map.get(request, "id")) - end - end) - end - - defp logs_blocks_filter(filter_options) do - with {:filter, %{"blockHash" => block_hash_param}} <- {:filter, filter_options}, - {:block_hash, {:ok, block_hash}} <- {:block_hash, Hash.Full.cast(block_hash_param)}, - {:block, %{number: number}} <- {:block, Repo.get(Block, block_hash)} do - {:ok, number, number} - else - {:filter, filters} -> - from_block = Map.get(filters, "fromBlock", "latest") - to_block = Map.get(filters, "toBlock", "latest") - - max_block_number = - if from_block == "latest" || to_block == "latest" do - max_consensus_block_number() - end - - pending_block_number = - if from_block == "pending" || to_block == "pending" do - max_non_consensus_block_number(max_block_number) - end - - if is_nil(pending_block_number) && from_block == "pending" && to_block == "pending" do - {:error, :empty} - else - to_block_numbers(from_block, to_block, max_block_number, pending_block_number) - end - - {:block, _} -> - {:error, "Invalid Block Hash"} - - {:block_hash, _} -> - {:error, "Invalid Block Hash"} - end - end - - defp to_block_numbers(from_block, to_block, max_block_number, pending_block_number) do - actual_pending_block_number = pending_block_number || max_block_number - - with {:ok, from} <- - to_block_number(from_block, max_block_number, actual_pending_block_number), - {:ok, to} <- to_block_number(to_block, max_block_number, actual_pending_block_number) do - {:ok, from, to} - end - end - - defp to_block_number(integer, _, _) when is_integer(integer), do: {:ok, integer} - defp to_block_number("latest", max_block_number, _), do: {:ok, max_block_number || 0} - defp to_block_number("earliest", _, _), do: {:ok, 0} - defp to_block_number("pending", max_block_number, nil), do: {:ok, max_block_number || 0} - defp to_block_number("pending", _, pending), do: {:ok, pending} - - defp to_block_number("0x" <> number, _, _) do - case Integer.parse(number, 16) do - {integer, ""} -> {:ok, integer} - _ -> {:error, "invalid block number"} - end - end - - defp to_block_number(number, _, _) when is_bitstring(number) do - case Integer.parse(number, 16) do - {integer, ""} -> {:ok, integer} - _ -> {:error, "invalid block number"} - end - end - - defp to_block_number(_, _, _), do: {:error, "invalid block number"} - - defp max_non_consensus_block_number(max) do - case Chain.max_non_consensus_block_number(max) do - {:ok, number} -> number - _ -> nil - end - end - - defp max_consensus_block_number do - case Chain.max_consensus_block_number() do - {:ok, number} -> number - _ -> nil - end - end - - defp format_success(result, id) do - %{result: result, id: id} - end - - defp format_error(message, id) do - %{error: message, id: id} - end - - defp do_eth_request(%{"jsonrpc" => rpc_version}) when rpc_version != "2.0" do - {:error, "invalid rpc version"} - end - - defp do_eth_request(%{"jsonrpc" => "2.0", "method" => method, "params" => params}) - when is_list(params) do - with {:ok, action} <- get_action(method), - {:correct_arity, true} <- - {:correct_arity, :erlang.function_exported(__MODULE__, action, Enum.count(params))} do - apply(__MODULE__, action, params) - else - {:correct_arity, _} -> - {:error, "Incorrect number of params."} - - _ -> - {:error, "Action not found."} - end - end - - defp do_eth_request(%{"params" => _params, "method" => _}) do - {:error, "Invalid params. Params must be a list."} - end - - defp do_eth_request(_) do - {:error, "Method, params, and jsonrpc, are all required parameters."} - end - - defp get_action(action) do - case Map.get(@methods, action) do - %{action: action} -> - {:ok, action} - - _ -> - :error - end - end - - defp block_param("latest"), do: {:ok, :latest} - defp block_param("earliest"), do: {:ok, :earliest} - defp block_param("pending"), do: {:ok, :pending} - - defp block_param(string_integer) when is_bitstring(string_integer) do - case Integer.parse(string_integer) do - {integer, ""} -> {:ok, integer} - _ -> :error - end - end - - defp block_param(nil), do: {:ok, :latest} - defp block_param(_), do: :error end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api_docs_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api_docs_controller.ex index 9309884d5c..4a658c409e 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api_docs_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api_docs_controller.ex @@ -1,8 +1,8 @@ defmodule BlockScoutWeb.APIDocsController do use BlockScoutWeb, :controller - alias BlockScoutWeb.API.RPC.EthController alias BlockScoutWeb.Etherscan + alias Explorer.EthRPC def index(conn, _params) do conn @@ -12,7 +12,7 @@ defmodule BlockScoutWeb.APIDocsController do def eth_rpc(conn, _params) do conn - |> assign(:documentation, EthController.methods()) + |> assign(:documentation, EthRPC.methods()) |> render("eth_rpc.html") end end diff --git a/apps/explorer/lib/explorer/eth_rpc.ex b/apps/explorer/lib/explorer/eth_rpc.ex new file mode 100644 index 0000000000..4aa7aaf187 --- /dev/null +++ b/apps/explorer/lib/explorer/eth_rpc.ex @@ -0,0 +1,322 @@ +defmodule Explorer.EthRPC do + @moduledoc """ + Ethreum JSON RPC methods logic implementation. + """ + + alias Ecto.Type, as: EctoType + alias Explorer.{Chain, Repo} + alias Explorer.Chain.{Block, Data, Hash, Hash.Address, Wei} + alias Explorer.Etherscan.Logs + + @methods %{ + "eth_getBalance" => %{ + action: :eth_get_balance, + notes: """ + the `earliest` parameter will not work as expected currently, because genesis block balances + are not currently imported + """ + }, + "eth_getLogs" => %{ + action: :eth_get_logs, + notes: """ + Will never return more than 1000 log entries. + """ + } + } + + @index_to_word %{ + 0 => "first", + 1 => "second", + 2 => "third", + 3 => "fourth" + } + + def responses(requests) do + Enum.map(requests, fn request -> + with {:id, {:ok, id}} <- {:id, Map.fetch(request, "id")}, + {:request, {:ok, result}} <- {:request, do_eth_request(request)} do + format_success(result, id) + else + {:id, :error} -> format_error("id is a required field", 0) + {:request, {:error, message}} -> format_error(message, Map.get(request, "id")) + end + end) + end + + def eth_get_balance(address_param, block_param \\ nil) do + with {:address, {:ok, address}} <- {:address, Chain.string_to_address_hash(address_param)}, + {:block, {:ok, block}} <- {:block, block_param(block_param)}, + {:balance, {:ok, balance}} <- {:balance, Chain.get_balance_as_of_block(address, block)} do + {:ok, Wei.hex_format(balance)} + else + {:address, :error} -> + {:error, "Query parameter 'address' is invalid"} + + {:block, :error} -> + {:error, "Query parameter 'block' is invalid"} + + {:balance, {:error, :not_found}} -> + {:error, "Balance not found"} + end + end + + def eth_get_logs(filter_options) do + with {:ok, address_or_topic_params} <- address_or_topic_params(filter_options), + {:ok, from_block_param, to_block_param} <- logs_blocks_filter(filter_options), + {:ok, from_block} <- cast_block(from_block_param), + {:ok, to_block} <- cast_block(to_block_param) do + filter = + address_or_topic_params + |> Map.put(:from_block, from_block) + |> Map.put(:to_block, to_block) + |> Map.put(:allow_non_consensus, true) + + {:ok, filter |> Logs.list_logs() |> Enum.map(&render_log/1)} + else + {:error, message} when is_bitstring(message) -> + {:error, message} + + {:error, :empty} -> + {:ok, []} + + _ -> + {:error, "Something went wrong."} + end + end + + defp render_log(log) do + topics = + Enum.reject( + [log.first_topic, log.second_topic, log.third_topic, log.fourth_topic], + &is_nil/1 + ) + + %{ + "address" => to_string(log.address_hash), + "blockHash" => to_string(log.block_hash), + "blockNumber" => Integer.to_string(log.block_number, 16), + "data" => to_string(log.data), + "logIndex" => Integer.to_string(log.index, 16), + "removed" => log.block_consensus == false, + "topics" => topics, + "transactionHash" => to_string(log.transaction_hash), + "transactionIndex" => log.transaction_index, + "transactionLogIndex" => log.index, + "type" => "mined" + } + end + + defp cast_block("0x" <> hexadecimal_digits = input) do + case Integer.parse(hexadecimal_digits, 16) do + {integer, ""} -> {:ok, integer} + _ -> {:error, input <> " is not a valid block number"} + end + end + + defp cast_block(integer) when is_integer(integer), do: {:ok, integer} + defp cast_block(_), do: {:error, "invalid block number"} + + defp address_or_topic_params(filter_options) do + address_param = Map.get(filter_options, "address") + topics_param = Map.get(filter_options, "topics") + + with {:ok, address} <- validate_address(address_param), + {:ok, topics} <- validate_topics(topics_param) do + address_and_topics(address, topics) + end + end + + defp address_and_topics(nil, nil), do: {:error, "Must supply one of address and topics"} + defp address_and_topics(address, nil), do: {:ok, %{address_hash: address}} + defp address_and_topics(nil, topics), do: {:ok, topics} + defp address_and_topics(address, topics), do: {:ok, Map.put(topics, :address_hash, address)} + + defp validate_address(nil), do: {:ok, nil} + + defp validate_address(address) do + case Address.cast(address) do + {:ok, address} -> {:ok, address} + :error -> {:error, "invalid address"} + end + end + + defp validate_topics(nil), do: {:ok, nil} + defp validate_topics([]), do: [] + + defp validate_topics(topics) when is_list(topics) do + topics + |> Stream.with_index() + |> Enum.reduce({:ok, %{}}, fn {topic, index}, {:ok, acc} -> + case cast_topics(topic) do + {:ok, data} -> + with_filter = Map.put(acc, String.to_existing_atom("#{@index_to_word[index]}_topic"), data) + + {:ok, add_operator(with_filter, index)} + + :error -> + {:error, "invalid topics"} + end + end) + end + + defp add_operator(filters, 0), do: filters + + defp add_operator(filters, index) do + Map.put(filters, String.to_existing_atom("topic#{index - 1}_#{index}_opr"), "and") + end + + defp cast_topics(topics) when is_list(topics) do + case EctoType.cast({:array, Data}, topics) do + {:ok, data} -> {:ok, Enum.map(data, &to_string/1)} + :error -> :error + end + end + + defp cast_topics(topic) do + case Data.cast(topic) do + {:ok, data} -> {:ok, to_string(data)} + :error -> :error + end + end + + defp logs_blocks_filter(filter_options) do + with {:filter, %{"blockHash" => block_hash_param}} <- {:filter, filter_options}, + {:block_hash, {:ok, block_hash}} <- {:block_hash, Hash.Full.cast(block_hash_param)}, + {:block, %{number: number}} <- {:block, Repo.get(Block, block_hash)} do + {:ok, number, number} + else + {:filter, filters} -> + from_block = Map.get(filters, "fromBlock", "latest") + to_block = Map.get(filters, "toBlock", "latest") + + max_block_number = + if from_block == "latest" || to_block == "latest" do + max_consensus_block_number() + end + + pending_block_number = + if from_block == "pending" || to_block == "pending" do + max_non_consensus_block_number(max_block_number) + end + + if is_nil(pending_block_number) && from_block == "pending" && to_block == "pending" do + {:error, :empty} + else + to_block_numbers(from_block, to_block, max_block_number, pending_block_number) + end + + {:block, _} -> + {:error, "Invalid Block Hash"} + + {:block_hash, _} -> + {:error, "Invalid Block Hash"} + end + end + + defp to_block_numbers(from_block, to_block, max_block_number, pending_block_number) do + actual_pending_block_number = pending_block_number || max_block_number + + with {:ok, from} <- + to_block_number(from_block, max_block_number, actual_pending_block_number), + {:ok, to} <- to_block_number(to_block, max_block_number, actual_pending_block_number) do + {:ok, from, to} + end + end + + defp to_block_number(integer, _, _) when is_integer(integer), do: {:ok, integer} + defp to_block_number("latest", max_block_number, _), do: {:ok, max_block_number || 0} + defp to_block_number("earliest", _, _), do: {:ok, 0} + defp to_block_number("pending", max_block_number, nil), do: {:ok, max_block_number || 0} + defp to_block_number("pending", _, pending), do: {:ok, pending} + + defp to_block_number("0x" <> number, _, _) do + case Integer.parse(number, 16) do + {integer, ""} -> {:ok, integer} + _ -> {:error, "invalid block number"} + end + end + + defp to_block_number(number, _, _) when is_bitstring(number) do + case Integer.parse(number, 16) do + {integer, ""} -> {:ok, integer} + _ -> {:error, "invalid block number"} + end + end + + defp to_block_number(_, _, _), do: {:error, "invalid block number"} + + defp max_non_consensus_block_number(max) do + case Chain.max_non_consensus_block_number(max) do + {:ok, number} -> number + _ -> nil + end + end + + defp max_consensus_block_number do + case Chain.max_consensus_block_number() do + {:ok, number} -> number + _ -> nil + end + end + + defp format_success(result, id) do + %{result: result, id: id} + end + + defp format_error(message, id) do + %{error: message, id: id} + end + + defp do_eth_request(%{"jsonrpc" => rpc_version}) when rpc_version != "2.0" do + {:error, "invalid rpc version"} + end + + defp do_eth_request(%{"jsonrpc" => "2.0", "method" => method, "params" => params}) + when is_list(params) do + with {:ok, action} <- get_action(method), + {:correct_arity, true} <- + {:correct_arity, :erlang.function_exported(__MODULE__, action, Enum.count(params))} do + apply(__MODULE__, action, params) + else + {:correct_arity, _} -> + {:error, "Incorrect number of params."} + + _ -> + {:error, "Action not found."} + end + end + + defp do_eth_request(%{"params" => _params, "method" => _}) do + {:error, "Invalid params. Params must be a list."} + end + + defp do_eth_request(_) do + {:error, "Method, params, and jsonrpc, are all required parameters."} + end + + defp get_action(action) do + case Map.get(@methods, action) do + %{action: action} -> + {:ok, action} + + _ -> + :error + end + end + + defp block_param("latest"), do: {:ok, :latest} + defp block_param("earliest"), do: {:ok, :earliest} + defp block_param("pending"), do: {:ok, :pending} + + defp block_param(string_integer) when is_bitstring(string_integer) do + case Integer.parse(string_integer) do + {integer, ""} -> {:ok, integer} + _ -> :error + end + end + + defp block_param(nil), do: {:ok, :latest} + defp block_param(_), do: :error + + def methods, do: @methods +end From 9a95541797901a4bf029c4fea79f9f46f979b947 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 16 Jul 2019 12:30:01 +0300 Subject: [PATCH 03/24] sort records for pagination --- apps/explorer/lib/explorer/etherscan/logs.ex | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/apps/explorer/lib/explorer/etherscan/logs.ex b/apps/explorer/lib/explorer/etherscan/logs.ex index 921ae1bcdb..fa3ea646c3 100644 --- a/apps/explorer/lib/explorer/etherscan/logs.ex +++ b/apps/explorer/lib/explorer/etherscan/logs.ex @@ -5,7 +5,7 @@ defmodule Explorer.Etherscan.Logs do """ - import Ecto.Query, only: [from: 2, where: 3, subquery: 1] + import Ecto.Query, only: [from: 2, where: 3, subquery: 1, order_by: 3] alias Explorer.Chain.{Block, InternalTransaction, Log, Transaction} alias Explorer.Repo @@ -38,7 +38,7 @@ defmodule Explorer.Etherscan.Logs do :type ] - @default_paging_options %{block_number: nil, transaction_index: nil} + @default_paging_options %{block_number: nil, transaction_index: nil, log_index: nil} @doc """ Gets a list of logs that meet the criteria in a given filter map. @@ -240,5 +240,18 @@ defmodule Explorer.Etherscan.Logs do defp where_multiple_topics_match(query, _, _, _), do: query - defp page_logs(query, _paging_options), do: query + defp page_logs(query, %{block_number: nil, transaction_index: nil, log_index: nil}) do + query + |> order_by([log], asc: log.index) + end + + defp page_logs(query, %{block_number: block_number, transaction_index: transaction_index, log_index: log_index}) do + sorted_query = order_by(query, [log], asc: log.index) + + from(data in sorted_query, + where: + data.index > ^log_index and data.block_number >= ^block_number and + data.transaction_index >= ^transaction_index + ) + end end From 9ada146563c784f949e6915bada1bc630b7b1fb4 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 16 Jul 2019 12:44:53 +0300 Subject: [PATCH 04/24] add pagination test --- .../test/explorer/etherscan/logs_test.exs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/apps/explorer/test/explorer/etherscan/logs_test.exs b/apps/explorer/test/explorer/etherscan/logs_test.exs index 00050db6f4..a8efb71ac9 100644 --- a/apps/explorer/test/explorer/etherscan/logs_test.exs +++ b/apps/explorer/test/explorer/etherscan/logs_test.exs @@ -158,6 +158,46 @@ defmodule Explorer.Etherscan.LogsTest do assert found_log.transaction_hash == transaction_block1.hash end + test "paginates logs" do + contract_address = insert(:contract_address) + + transaction = + %Transaction{block: block} = + :transaction + |> insert(to_address: contract_address) + |> with_block() + + inserted_records = insert_list(2000, :log, address: contract_address, transaction: transaction) + + filter = %{ + from_block: block.number, + to_block: block.number, + address_hash: contract_address.hash + } + + first_found_logs = Logs.list_logs(filter) + + assert Enum.count(first_found_logs) == 1_000 + + last_record = List.last(first_found_logs) + + next_page_params = %{ + log_index: last_record.index, + transaction_index: last_record.transaction_index, + block_number: transaction.block_number + } + + second_found_logs = Logs.list_logs(filter, next_page_params) + + assert Enum.count(second_found_logs) == 1_000 + + all_found_logs = first_found_logs ++ second_found_logs + + Enum.all?(inserted_records, fn record -> + Enum.any?(all_found_logs, fn found_log -> found_log.index == record.index end) + end) + end + test "with a valid topic{x}" do contract_address = insert(:contract_address) From a137af13dccd4e580526fd319418692cc599e2ca Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 16 Jul 2019 13:38:58 +0300 Subject: [PATCH 05/24] add pagination params to endpoint --- .../api/rpc/eth_controller_test.exs | 48 +++++++++++++++++++ apps/explorer/lib/explorer/eth_rpc.ex | 25 +++++++++- apps/explorer/lib/explorer/etherscan/logs.ex | 2 + 3 files changed, 73 insertions(+), 2 deletions(-) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs index 1b9273c66c..dafaed6bb3 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs @@ -125,6 +125,54 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do assert [%{"data" => "0x010101"}, %{"data" => "0x020202"}] = Enum.sort_by(response["result"], &Map.get(&1, "data")) end + test "paginates logs", %{conn: conn, api_params: api_params} do + contract_address = insert(:contract_address) + + transaction = + :transaction + |> insert(to_address: contract_address) + |> with_block() + + inserted_records = + insert_list(2000, :log, address: contract_address, transaction: transaction, first_topic: "0x01") + + params = params(api_params, [%{"address" => to_string(contract_address), "topics" => [["0x01"]]}]) + + assert response = + conn + |> post("/api/eth_rpc", params) + |> json_response(200) + + assert Enum.count(response["result"]) == 1000 + + next_page_params = %{ + "block_number" => transaction.block_number, + "transaction_index" => transaction.index, + "log_index" => 999 + } + + new_params = + params(api_params, [ + %{"paging_options" => next_page_params, "address" => to_string(contract_address), "topics" => [["0x01"]]} + ]) + + assert new_response = + conn + |> post("/api/eth_rpc", new_params) + |> json_response(200) + + assert Enum.count(response["result"]) == 1000 + + all_found_logs = response["result"] ++ new_response["result"] + + assert Enum.all?(inserted_records, fn record -> + Enum.any?(all_found_logs, fn found_log -> + {index, ""} = Integer.parse(found_log["logIndex"], 16) + record.index == index + end) + end) + end + test "with a matching address and multiple topic matches in different positions", %{ conn: conn, api_params: api_params diff --git a/apps/explorer/lib/explorer/eth_rpc.ex b/apps/explorer/lib/explorer/eth_rpc.ex index 4aa7aaf187..0bd07849d5 100644 --- a/apps/explorer/lib/explorer/eth_rpc.ex +++ b/apps/explorer/lib/explorer/eth_rpc.ex @@ -64,14 +64,20 @@ defmodule Explorer.EthRPC do with {:ok, address_or_topic_params} <- address_or_topic_params(filter_options), {:ok, from_block_param, to_block_param} <- logs_blocks_filter(filter_options), {:ok, from_block} <- cast_block(from_block_param), - {:ok, to_block} <- cast_block(to_block_param) do + {:ok, to_block} <- cast_block(to_block_param), + {:ok, paging_options} <- paging_options(filter_options["paging_options"]) do filter = address_or_topic_params |> Map.put(:from_block, from_block) |> Map.put(:to_block, to_block) |> Map.put(:allow_non_consensus, true) - {:ok, filter |> Logs.list_logs() |> Enum.map(&render_log/1)} + logs = + filter + |> Logs.list_logs(paging_options) + |> Enum.map(&render_log/1) + + {:ok, logs} else {:error, message} when is_bitstring(message) -> {:error, message} @@ -213,6 +219,21 @@ defmodule Explorer.EthRPC do end end + defp paging_options(%{ + "log_index" => log_index, + "transaction_index" => transaction_index, + "block_number" => block_number + }) do + {:ok, + %{ + log_index: log_index, + transaction_index: transaction_index, + block_number: block_number + }} + end + + defp paging_options(_), do: {:ok, nil} + defp to_block_numbers(from_block, to_block, max_block_number, pending_block_number) do actual_pending_block_number = pending_block_number || max_block_number diff --git a/apps/explorer/lib/explorer/etherscan/logs.ex b/apps/explorer/lib/explorer/etherscan/logs.ex index fa3ea646c3..67b754156d 100644 --- a/apps/explorer/lib/explorer/etherscan/logs.ex +++ b/apps/explorer/lib/explorer/etherscan/logs.ex @@ -73,6 +73,7 @@ defmodule Explorer.Etherscan.Logs do def list_logs(filter, paging_options \\ @default_paging_options) def list_logs(%{address_hash: address_hash} = filter, paging_options) when not is_nil(address_hash) do + paging_options = if is_nil(paging_options), do: @default_paging_options, else: paging_options prepared_filter = Map.merge(@base_filter, filter) logs_query = where_topic_match(Log, prepared_filter) @@ -148,6 +149,7 @@ defmodule Explorer.Etherscan.Logs do # query that is optimized for a logs filter over an # address_hash def list_logs(filter, paging_options) do + paging_options = if is_nil(paging_options), do: @default_paging_options, else: paging_options prepared_filter = Map.merge(@base_filter, filter) logs_query = where_topic_match(Log, prepared_filter) From 48db99478719563ad4bf5e6ef1eb8382c31c6a19 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 16 Jul 2019 13:42:43 +0300 Subject: [PATCH 06/24] fix conflicts --- apps/explorer/lib/explorer/eth_rpc.ex | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/explorer/lib/explorer/eth_rpc.ex b/apps/explorer/lib/explorer/eth_rpc.ex index 0bd07849d5..6acb7f523f 100644 --- a/apps/explorer/lib/explorer/eth_rpc.ex +++ b/apps/explorer/lib/explorer/eth_rpc.ex @@ -14,12 +14,18 @@ defmodule Explorer.EthRPC do notes: """ the `earliest` parameter will not work as expected currently, because genesis block balances are not currently imported + """, + example: """ + {"id": 0, "jsonrpc": "2.0", "method": "eth_getBalance", "params": ["0x0000000000000000000000000000000000000007", "2"]} """ }, "eth_getLogs" => %{ action: :eth_get_logs, notes: """ Will never return more than 1000 log entries. + """, + example: """ + {"id": 0, "jsonrpc": "2.0", "method": "eth_getLogs", "params": [{"address": "0x0000000000000000000000000000000000000026","topics": ["0x01"]}]} """ } } From 8e23dc553425c767dc684cd991a9275d3bdf8a83 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 16 Jul 2019 13:53:34 +0300 Subject: [PATCH 07/24] fix test --- .../controllers/api/rpc/eth_controller_test.exs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs index dafaed6bb3..f6fc4b79d1 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs @@ -148,7 +148,7 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do next_page_params = %{ "block_number" => transaction.block_number, "transaction_index" => transaction.index, - "log_index" => 999 + "log_index" => 1000 } new_params = @@ -168,6 +168,7 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do assert Enum.all?(inserted_records, fn record -> Enum.any?(all_found_logs, fn found_log -> {index, ""} = Integer.parse(found_log["logIndex"], 16) + record.index == index end) end) From 2d2bdfc4ed3ad5a41dfe2041b849e7b02950a15b Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 16 Jul 2019 14:02:21 +0300 Subject: [PATCH 08/24] add note about pagination params --- apps/explorer/lib/explorer/eth_rpc.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/eth_rpc.ex b/apps/explorer/lib/explorer/eth_rpc.ex index 6acb7f523f..aaec38f4f6 100644 --- a/apps/explorer/lib/explorer/eth_rpc.ex +++ b/apps/explorer/lib/explorer/eth_rpc.ex @@ -22,7 +22,8 @@ defmodule Explorer.EthRPC do "eth_getLogs" => %{ action: :eth_get_logs, notes: """ - Will never return more than 1000 log entries. + Will never return more than 1000 log entries.\n + For this reason, you can use pagination options to request the next page. Pagination options params: {"block_number": 1, "transaction_index": 0, "log_index": 1} which include parameters from the last log received from the previous request. """, example: """ {"id": 0, "jsonrpc": "2.0", "method": "eth_getLogs", "params": [{"address": "0x0000000000000000000000000000000000000026","topics": ["0x01"]}]} From 75a7a1d496b4f737c21ca57aec955d59b5e1a525 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 16 Jul 2019 14:09:34 +0300 Subject: [PATCH 09/24] use dynamic log index --- .../controllers/api/rpc/eth_controller_test.exs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs index f6fc4b79d1..d611d519ad 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs @@ -145,10 +145,12 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do assert Enum.count(response["result"]) == 1000 + {last_log_index, ""} = Integer.parse(List.last(response["result"])["logIndex"], 16) + next_page_params = %{ "block_number" => transaction.block_number, "transaction_index" => transaction.index, - "log_index" => 1000 + "log_index" => last_log_index } new_params = From 4ea795d1b1f6e1f3925732ae6cb8efff9e3f1b46 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 16 Jul 2019 14:12:28 +0300 Subject: [PATCH 10/24] add forgotten assert --- apps/explorer/test/explorer/etherscan/logs_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/explorer/test/explorer/etherscan/logs_test.exs b/apps/explorer/test/explorer/etherscan/logs_test.exs index a8efb71ac9..d1d3f8151c 100644 --- a/apps/explorer/test/explorer/etherscan/logs_test.exs +++ b/apps/explorer/test/explorer/etherscan/logs_test.exs @@ -193,7 +193,7 @@ defmodule Explorer.Etherscan.LogsTest do all_found_logs = first_found_logs ++ second_found_logs - Enum.all?(inserted_records, fn record -> + assert Enum.all?(inserted_records, fn record -> Enum.any?(all_found_logs, fn found_log -> found_log.index == record.index end) end) end From 1c344bcf99c441fd67124d2dac41082c32674d47 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 16 Jul 2019 14:19:30 +0300 Subject: [PATCH 11/24] mix format --- apps/explorer/test/explorer/etherscan/logs_test.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/explorer/test/explorer/etherscan/logs_test.exs b/apps/explorer/test/explorer/etherscan/logs_test.exs index d1d3f8151c..490dce199d 100644 --- a/apps/explorer/test/explorer/etherscan/logs_test.exs +++ b/apps/explorer/test/explorer/etherscan/logs_test.exs @@ -194,8 +194,8 @@ defmodule Explorer.Etherscan.LogsTest do all_found_logs = first_found_logs ++ second_found_logs assert Enum.all?(inserted_records, fn record -> - Enum.any?(all_found_logs, fn found_log -> found_log.index == record.index end) - end) + Enum.any?(all_found_logs, fn found_log -> found_log.index == record.index end) + end) end test "with a valid topic{x}" do From 1f2ab38b28a4f186c8990fcebbc13ab6e385eda2 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 16 Jul 2019 16:40:05 +0300 Subject: [PATCH 12/24] add CHANGELOG entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cd9165b22..abd6928bfd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## Current ### Features +- [#2366)(https://github.com/poanetwork/blockscout/pull/2366) - paginate eth logs - [#2352](https://github.com/poanetwork/blockscout/pull/2352) - Fetch rewards in parallel with transactions - [#2294](https://github.com/poanetwork/blockscout/pull/2294) - add healthy block period checking endpoint From 65492905df40f4d5c566be8513ba4fea906f3027 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 18 Jul 2019 12:41:45 +0300 Subject: [PATCH 13/24] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index abd6928bfd..e450270bee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ ## Current ### Features -- [#2366)(https://github.com/poanetwork/blockscout/pull/2366) - paginate eth logs +- [#2366](https://github.com/poanetwork/blockscout/pull/2366) - paginate eth logs - [#2352](https://github.com/poanetwork/blockscout/pull/2352) - Fetch rewards in parallel with transactions - [#2294](https://github.com/poanetwork/blockscout/pull/2294) - add healthy block period checking endpoint From 9919d404c82759d24046b7a314de53807d1395c5 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 18 Jul 2019 12:54:07 +0300 Subject: [PATCH 14/24] fix CR issues --- apps/explorer/lib/explorer/eth_rpc.ex | 10 ++++++---- apps/explorer/lib/explorer/etherscan/logs.ex | 8 ++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/explorer/lib/explorer/eth_rpc.ex b/apps/explorer/lib/explorer/eth_rpc.ex index aaec38f4f6..31e711d9fa 100644 --- a/apps/explorer/lib/explorer/eth_rpc.ex +++ b/apps/explorer/lib/explorer/eth_rpc.ex @@ -72,7 +72,7 @@ defmodule Explorer.EthRPC do {:ok, from_block_param, to_block_param} <- logs_blocks_filter(filter_options), {:ok, from_block} <- cast_block(from_block_param), {:ok, to_block} <- cast_block(to_block_param), - {:ok, paging_options} <- paging_options(filter_options["paging_options"]) do + {:ok, paging_options} <- paging_options(filter_options) do filter = address_or_topic_params |> Map.put(:from_block, from_block) @@ -227,9 +227,11 @@ defmodule Explorer.EthRPC do end defp paging_options(%{ - "log_index" => log_index, - "transaction_index" => transaction_index, - "block_number" => block_number + "paging_options" => %{ + "log_index" => log_index, + "transaction_index" => transaction_index, + "block_number" => block_number + } }) do {:ok, %{ diff --git a/apps/explorer/lib/explorer/etherscan/logs.ex b/apps/explorer/lib/explorer/etherscan/logs.ex index 67b754156d..336eae106b 100644 --- a/apps/explorer/lib/explorer/etherscan/logs.ex +++ b/apps/explorer/lib/explorer/etherscan/logs.ex @@ -140,6 +140,7 @@ defmodule Explorer.Etherscan.Logs do end query_with_consensus + |> order_by([log], asc: log.index) |> page_logs(paging_options) |> Repo.all() end @@ -191,6 +192,7 @@ defmodule Explorer.Etherscan.Logs do ) query_with_block_transaction_data + |> order_by([log], asc: log.index) |> page_logs(paging_options) |> Repo.all() end @@ -244,13 +246,11 @@ defmodule Explorer.Etherscan.Logs do defp page_logs(query, %{block_number: nil, transaction_index: nil, log_index: nil}) do query - |> order_by([log], asc: log.index) end defp page_logs(query, %{block_number: block_number, transaction_index: transaction_index, log_index: log_index}) do - sorted_query = order_by(query, [log], asc: log.index) - - from(data in sorted_query, + from( + data in query, where: data.index > ^log_index and data.block_number >= ^block_number and data.transaction_index >= ^transaction_index From f187402f5e5cf08081947af7be5b62de71e081a8 Mon Sep 17 00:00:00 2001 From: Michael Ira Krufky Date: Thu, 18 Jul 2019 14:00:01 -0400 Subject: [PATCH 15/24] docker/Dockerfile: pin bitwalker/alpine-elixir-phoenix:1.9.0 --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 73501a9b9d..f73c4b34f0 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM bitwalker/alpine-elixir-phoenix +FROM bitwalker/alpine-elixir-phoenix:1.9.0 RUN apk --no-cache --update add alpine-sdk gmp-dev automake libtool inotify-tools autoconf python From 268b39815e81e5fe3e04c23d7699a0d850bf71c9 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Fri, 19 Jul 2019 13:22:40 +0300 Subject: [PATCH 16/24] Drastically reduce lodash lib --- CHANGELOG.md | 1 + .../__tests__/pages/pending_transactions.js | 1 - .../assets/js/lib/async_listing_load.js | 7 +++-- .../assets/js/lib/infinite_scroll_helpers.js | 4 +-- .../assets/js/lib/list_morph.js | 29 +++++++++++-------- .../assets/js/lib/redux_helpers.js | 10 ++++--- apps/block_scout_web/assets/js/lib/utils.js | 4 +-- .../assets/js/pages/address.js | 4 +-- .../assets/js/pages/address/coin_balances.js | 4 +-- .../js/pages/address/internal_transactions.js | 4 +-- .../assets/js/pages/address/logs.js | 4 +-- .../assets/js/pages/address/transactions.js | 4 +-- .../assets/js/pages/address/validations.js | 4 +-- .../block_scout_web/assets/js/pages/blocks.js | 19 +++++++----- apps/block_scout_web/assets/js/pages/chain.js | 18 +++++++----- .../assets/js/pages/pending_transactions.js | 4 +-- .../assets/js/pages/transaction.js | 4 +-- .../assets/js/pages/transactions.js | 4 +-- .../assets/js/pages/verification_form.js | 4 +-- apps/block_scout_web/assets/webpack.config.js | 2 +- 20 files changed, 76 insertions(+), 59 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9c8934f3d..b5dfb7a6fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - [#2294](https://github.com/poanetwork/blockscout/pull/2294) - add healthy block period checking endpoint ### Fixes +- [#2389](https://github.com/poanetwork/blockscout/pull/2389) - Reduce Lodash lib size (86% of lib methods are not used) - [#2378](https://github.com/poanetwork/blockscout/pull/2378) - Page performance: exclude moment.js localization files except EN, remove unused css - [#2368](https://github.com/poanetwork/blockscout/pull/2368) - add two columns of smart contract info - [#2375](https://github.com/poanetwork/blockscout/pull/2375) - Update created_contract_code_indexed_at on transaction import conflict diff --git a/apps/block_scout_web/assets/__tests__/pages/pending_transactions.js b/apps/block_scout_web/assets/__tests__/pages/pending_transactions.js index e9f7fc941d..ddbf84ce7d 100644 --- a/apps/block_scout_web/assets/__tests__/pages/pending_transactions.js +++ b/apps/block_scout_web/assets/__tests__/pages/pending_transactions.js @@ -1,4 +1,3 @@ -import _ from 'lodash' import { reducer, initialState } from '../../js/pages/pending_transactions' test('CHANNEL_DISCONNECTED', () => { diff --git a/apps/block_scout_web/assets/js/lib/async_listing_load.js b/apps/block_scout_web/assets/js/lib/async_listing_load.js index 8610d901f6..8477a7d728 100644 --- a/apps/block_scout_web/assets/js/lib/async_listing_load.js +++ b/apps/block_scout_web/assets/js/lib/async_listing_load.js @@ -1,5 +1,6 @@ import $ from 'jquery' -import _ from 'lodash' +import map from 'lodash/map' +import merge from 'lodash/merge' import URI from 'urijs' import humps from 'humps' import listMorph from '../lib/list_morph' @@ -164,7 +165,7 @@ export const elements = { if (state.itemKey) { const container = $el[0] - const newElements = _.map(state.items, (item) => $(item)[0]) + const newElements = map(state.items, (item) => $(item)[0]) listMorph(container, newElements, { key: state.itemKey }) return } @@ -244,7 +245,7 @@ export const elements = { * adding or removing with the correct animation. Check list_morph.js for more informantion. */ export function createAsyncLoadStore (reducer, initialState, itemKey) { - const state = _.merge(asyncInitialState, initialState) + const state = merge(asyncInitialState, initialState) const store = createStore(reduceReducers(asyncReducer, reducer, state)) if (typeof itemKey !== 'undefined') { diff --git a/apps/block_scout_web/assets/js/lib/infinite_scroll_helpers.js b/apps/block_scout_web/assets/js/lib/infinite_scroll_helpers.js index 45b278ae4d..b7fbb940a2 100644 --- a/apps/block_scout_web/assets/js/lib/infinite_scroll_helpers.js +++ b/apps/block_scout_web/assets/js/lib/infinite_scroll_helpers.js @@ -1,5 +1,5 @@ import $ from 'jquery' -import _ from 'lodash' +import omit from 'lodash/omit' import humps from 'humps' import { connectElements } from './redux_helpers.js' @@ -12,7 +12,7 @@ const initialState = { function infiniteScrollReducer (state = initialState, action) { switch (action.type) { case 'INFINITE_SCROLL_ELEMENTS_LOAD': { - return Object.assign({}, state, _.omit(action, 'type')) + return Object.assign({}, state, omit(action, 'type')) } case 'LOADING_NEXT_PAGE': { return Object.assign({}, state, { diff --git a/apps/block_scout_web/assets/js/lib/list_morph.js b/apps/block_scout_web/assets/js/lib/list_morph.js index 175acb7c3f..6cdfea3e3f 100644 --- a/apps/block_scout_web/assets/js/lib/list_morph.js +++ b/apps/block_scout_web/assets/js/lib/list_morph.js @@ -1,5 +1,10 @@ import $ from 'jquery' -import _ from 'lodash' +import map from 'lodash/map' +import get from 'lodash/get' +import noop from 'lodash/noop' +import find from 'lodash/find' +import intersectionBy from 'lodash/intersectionBy' +import differenceBy from 'lodash/differenceBy' import morph from 'nanomorph' import { updateAllAges } from './from_now' @@ -25,12 +30,12 @@ import { updateAllAges } from './from_now' export default function (container, newElements, { key, horizontal } = {}) { if (!container) return const oldElements = $(container).children().get() - let currentList = _.map(oldElements, (el) => ({ id: _.get(el, key), el })) - const newList = _.map(newElements, (el) => ({ id: _.get(el, key), el })) - const overlap = _.intersectionBy(newList, currentList, 'id').map(({ id, el }) => ({ id, el: updateAllAges($(el))[0] })) + let currentList = map(oldElements, (el) => ({ id: get(el, key), el })) + const newList = map(newElements, (el) => ({ id: get(el, key), el })) + const overlap = intersectionBy(newList, currentList, 'id').map(({ id, el }) => ({ id, el: updateAllAges($(el))[0] })) // remove old items - const removals = _.differenceBy(currentList, newList, 'id') + const removals = differenceBy(currentList, newList, 'id') let canAnimate = !horizontal && removals.length <= 1 removals.forEach(({ el }) => { if (!canAnimate) return el.remove() @@ -38,7 +43,7 @@ export default function (container, newElements, { key, horizontal } = {}) { $el.addClass('shrink-out') setTimeout(() => { slideUpRemove($el) }, 400) }) - currentList = _.differenceBy(currentList, removals, 'id') + currentList = differenceBy(currentList, removals, 'id') // update kept items currentList = currentList.map(({ el }, i) => ({ @@ -47,14 +52,14 @@ export default function (container, newElements, { key, horizontal } = {}) { })) // add new items - const finalList = newList.map(({ id, el }) => _.get(_.find(currentList, { id }), 'el', el)).reverse() + const finalList = newList.map(({ id, el }) => get(find(currentList, { id }), 'el', el)).reverse() canAnimate = !horizontal finalList.forEach((el, i) => { if (el.parentElement) return - if (!canAnimate) return container.insertBefore(el, _.get(finalList, `[${i - 1}]`)) + if (!canAnimate) return container.insertBefore(el, get(finalList, `[${i - 1}]`)) canAnimate = false - if (!_.get(finalList, `[${i - 1}]`)) return slideDownAppend($(container), el) - slideDownBefore($(_.get(finalList, `[${i - 1}]`)), el) + if (!get(finalList, `[${i - 1}]`)) return slideDownAppend($(container), el) + slideDownBefore($(get(finalList, `[${i - 1}]`)), el) }) } @@ -80,7 +85,7 @@ function slideUpRemove ($el) { }) } -function smarterSlideDown ($el, { insert = _.noop } = {}) { +function smarterSlideDown ($el, { insert = noop } = {}) { if (!$el.length) return const originalScrollHeight = document.body.scrollHeight const scrollPosition = window.scrollY @@ -100,7 +105,7 @@ function smarterSlideDown ($el, { insert = _.noop } = {}) { } } -function smarterSlideUp ($el, { complete = _.noop } = {}) { +function smarterSlideUp ($el, { complete = noop } = {}) { if (!$el.length) return const originalScrollHeight = document.body.scrollHeight const scrollPosition = window.scrollY diff --git a/apps/block_scout_web/assets/js/lib/redux_helpers.js b/apps/block_scout_web/assets/js/lib/redux_helpers.js index fdf7c659b3..35dcc65b2c 100644 --- a/apps/block_scout_web/assets/js/lib/redux_helpers.js +++ b/apps/block_scout_web/assets/js/lib/redux_helpers.js @@ -1,5 +1,7 @@ import $ from 'jquery' -import _ from 'lodash' +import reduce from 'lodash/reduce' +import isObject from 'lodash/isObject' +import forIn from 'lodash/forIn' import { createStore as reduxCreateStore } from 'redux' /** @@ -97,17 +99,17 @@ export function createStore (reducer) { */ export function connectElements ({ elements, store, action = 'ELEMENTS_LOAD' }) { function loadElements () { - return _.reduce(elements, (pageLoadParams, { load }, selector) => { + return reduce(elements, (pageLoadParams, { load }, selector) => { if (!load) return pageLoadParams const $el = $(selector) if (!$el.length) return pageLoadParams const morePageLoadParams = load($el, store) - return _.isObject(morePageLoadParams) ? Object.assign(pageLoadParams, morePageLoadParams) : pageLoadParams + return isObject(morePageLoadParams) ? Object.assign(pageLoadParams, morePageLoadParams) : pageLoadParams }, {}) } function renderElements (state, oldState) { - _.forIn(elements, ({ render }, selector) => { + forIn(elements, ({ render }, selector) => { if (!render) return const $el = $(selector) if (!$el.length) return diff --git a/apps/block_scout_web/assets/js/lib/utils.js b/apps/block_scout_web/assets/js/lib/utils.js index d3bd6e84d1..e275580812 100644 --- a/apps/block_scout_web/assets/js/lib/utils.js +++ b/apps/block_scout_web/assets/js/lib/utils.js @@ -1,8 +1,8 @@ -import _ from 'lodash' +import debounce from 'lodash/debounce' export function batchChannel (func) { let msgs = [] - const debouncedFunc = _.debounce(() => { + const debouncedFunc = debounce(() => { func.apply(this, [msgs]) msgs = [] }, 1000, { maxWait: 5000 }) diff --git a/apps/block_scout_web/assets/js/pages/address.js b/apps/block_scout_web/assets/js/pages/address.js index 5b35a0b9d7..ddafbcb1e2 100644 --- a/apps/block_scout_web/assets/js/pages/address.js +++ b/apps/block_scout_web/assets/js/pages/address.js @@ -1,5 +1,5 @@ import $ from 'jquery' -import _ from 'lodash' +import omit from 'lodash/omit' import URI from 'urijs' import humps from 'humps' import numeral from 'numeral' @@ -25,7 +25,7 @@ export function reducer (state = initialState, action) { switch (action.type) { case 'PAGE_LOAD': case 'ELEMENTS_LOAD': { - return Object.assign({}, state, _.omit(action, 'type')) + return Object.assign({}, state, omit(action, 'type')) } case 'CHANNEL_DISCONNECTED': { if (state.beyondPageOne) return state diff --git a/apps/block_scout_web/assets/js/pages/address/coin_balances.js b/apps/block_scout_web/assets/js/pages/address/coin_balances.js index 8c0100d3d2..b4523a6c64 100644 --- a/apps/block_scout_web/assets/js/pages/address/coin_balances.js +++ b/apps/block_scout_web/assets/js/pages/address/coin_balances.js @@ -1,5 +1,5 @@ import $ from 'jquery' -import _ from 'lodash' +import omit from 'lodash/omit' import humps from 'humps' import socket from '../../socket' import { connectElements } from '../../lib/redux_helpers.js' @@ -14,7 +14,7 @@ export function reducer (state, action) { switch (action.type) { case 'PAGE_LOAD': case 'ELEMENTS_LOAD': { - return Object.assign({}, state, _.omit(action, 'type')) + return Object.assign({}, state, omit(action, 'type')) } case 'CHANNEL_DISCONNECTED': { if (state.beyondPageOne) return state diff --git a/apps/block_scout_web/assets/js/pages/address/internal_transactions.js b/apps/block_scout_web/assets/js/pages/address/internal_transactions.js index aa95278c71..6c3d503256 100644 --- a/apps/block_scout_web/assets/js/pages/address/internal_transactions.js +++ b/apps/block_scout_web/assets/js/pages/address/internal_transactions.js @@ -1,5 +1,5 @@ import $ from 'jquery' -import _ from 'lodash' +import omit from 'lodash/omit' import humps from 'humps' import numeral from 'numeral' import socket from '../../socket' @@ -20,7 +20,7 @@ export function reducer (state, action) { switch (action.type) { case 'PAGE_LOAD': case 'ELEMENTS_LOAD': { - return Object.assign({}, state, _.omit(action, 'type')) + return Object.assign({}, state, omit(action, 'type')) } case 'CHANNEL_DISCONNECTED': { if (state.beyondPageOne) return state diff --git a/apps/block_scout_web/assets/js/pages/address/logs.js b/apps/block_scout_web/assets/js/pages/address/logs.js index 5b4cf8aecd..5c9bed9da2 100644 --- a/apps/block_scout_web/assets/js/pages/address/logs.js +++ b/apps/block_scout_web/assets/js/pages/address/logs.js @@ -1,5 +1,5 @@ import $ from 'jquery' -import _ from 'lodash' +import omit from 'lodash/omit' import humps from 'humps' import { connectElements } from '../../lib/redux_helpers.js' import { createAsyncLoadStore } from '../../lib/async_listing_load' @@ -13,7 +13,7 @@ export function reducer (state, action) { switch (action.type) { case 'PAGE_LOAD': case 'ELEMENTS_LOAD': { - return Object.assign({}, state, _.omit(action, 'type')) + return Object.assign({}, state, omit(action, 'type')) } case 'START_SEARCH': { return Object.assign({}, state, {pagesStack: [], isSearch: true}) diff --git a/apps/block_scout_web/assets/js/pages/address/transactions.js b/apps/block_scout_web/assets/js/pages/address/transactions.js index be70dcc3ba..ecd604e2d5 100644 --- a/apps/block_scout_web/assets/js/pages/address/transactions.js +++ b/apps/block_scout_web/assets/js/pages/address/transactions.js @@ -1,5 +1,5 @@ import $ from 'jquery' -import _ from 'lodash' +import omit from 'lodash/omit' import URI from 'urijs' import humps from 'humps' import { subscribeChannel } from '../../socket' @@ -16,7 +16,7 @@ export function reducer (state, action) { switch (action.type) { case 'PAGE_LOAD': case 'ELEMENTS_LOAD': { - return Object.assign({}, state, _.omit(action, 'type')) + return Object.assign({}, state, omit(action, 'type')) } case 'CHANNEL_DISCONNECTED': { if (state.beyondPageOne) return state diff --git a/apps/block_scout_web/assets/js/pages/address/validations.js b/apps/block_scout_web/assets/js/pages/address/validations.js index 6aca07a98b..d2456a183e 100644 --- a/apps/block_scout_web/assets/js/pages/address/validations.js +++ b/apps/block_scout_web/assets/js/pages/address/validations.js @@ -1,5 +1,5 @@ import $ from 'jquery' -import _ from 'lodash' +import omit from 'lodash/omit' import humps from 'humps' import socket from '../../socket' import { connectElements } from '../../lib/redux_helpers.js' @@ -14,7 +14,7 @@ export function reducer (state = initialState, action) { switch (action.type) { case 'PAGE_LOAD': case 'ELEMENTS_LOAD': { - return Object.assign({}, state, _.omit(action, 'type')) + return Object.assign({}, state, omit(action, 'type')) } case 'CHANNEL_DISCONNECTED': { return Object.assign({}, state, { channelDisconnected: true }) diff --git a/apps/block_scout_web/assets/js/pages/blocks.js b/apps/block_scout_web/assets/js/pages/blocks.js index 2103c18a89..4d9bdc05d9 100644 --- a/apps/block_scout_web/assets/js/pages/blocks.js +++ b/apps/block_scout_web/assets/js/pages/blocks.js @@ -1,5 +1,10 @@ import $ from 'jquery' -import _ from 'lodash' +import omit from 'lodash/omit' +import last from 'lodash/last' +import min from 'lodash/min' +import max from 'lodash/max' +import keys from 'lodash/keys' +import rangeRight from 'lodash/rangeRight' import humps from 'humps' import socket from '../socket' import { connectElements } from '../lib/redux_helpers.js' @@ -14,7 +19,7 @@ export const blockReducer = withMissingBlocks(baseReducer) function baseReducer (state = initialState, action) { switch (action.type) { case 'ELEMENTS_LOAD': { - return Object.assign({}, state, _.omit(action, 'type')) + return Object.assign({}, state, omit(action, 'type')) } case 'CHANNEL_DISCONNECTED': { return Object.assign({}, state, { @@ -25,7 +30,7 @@ function baseReducer (state = initialState, action) { if (state.channelDisconnected || state.beyondPageOne || state.blockType !== 'block') return state const blockNumber = getBlockNumber(action.msg.blockHtml) - const minBlock = getBlockNumber(_.last(state.items)) + const minBlock = getBlockNumber(last(state.items)) if (state.items.length && blockNumber < minBlock) return state @@ -62,12 +67,12 @@ function withMissingBlocks (reducer) { return acc }, {}) - const blockNumbers = _(blockNumbersToItems).keys().map(x => parseInt(x, 10)).value() - const minBlock = _.min(blockNumbers) - const maxBlock = _.max(blockNumbers) + const blockNumbers = keys(blockNumbersToItems).map(x => parseInt(x, 10)) + const minBlock = min(blockNumbers) + const maxBlock = max(blockNumbers) return Object.assign({}, result, { - items: _.rangeRight(minBlock, maxBlock + 1) + items: rangeRight(minBlock, maxBlock + 1) .map((blockNumber) => blockNumbersToItems[blockNumber] || placeHolderBlock(blockNumber)) }) } diff --git a/apps/block_scout_web/assets/js/pages/chain.js b/apps/block_scout_web/assets/js/pages/chain.js index f4cd0e6628..154c73e455 100644 --- a/apps/block_scout_web/assets/js/pages/chain.js +++ b/apps/block_scout_web/assets/js/pages/chain.js @@ -1,5 +1,9 @@ import $ from 'jquery' -import _ from 'lodash' +import omit from 'lodash/omit' +import first from 'lodash/first' +import rangeRight from 'lodash/rangeRight' +import find from 'lodash/find' +import map from 'lodash/map' import humps from 'humps' import numeral from 'numeral' import socket from '../socket' @@ -33,7 +37,7 @@ export const reducer = withMissingBlocks(baseReducer) function baseReducer (state = initialState, action) { switch (action.type) { case 'ELEMENTS_LOAD': { - return Object.assign({}, state, _.omit(action, 'type')) + return Object.assign({}, state, omit(action, 'type')) } case 'RECEIVED_NEW_ADDRESS_COUNT': { return Object.assign({}, state, { @@ -122,12 +126,12 @@ function withMissingBlocks (reducer) { if (!result.blocks || result.blocks.length < 2) return result - const maxBlock = _.first(result.blocks).blockNumber + const maxBlock = first(result.blocks).blockNumber const minBlock = maxBlock - (result.blocks.length - 1) return Object.assign({}, result, { - blocks: _.rangeRight(minBlock, maxBlock + 1) - .map((blockNumber) => _.find(result.blocks, ['blockNumber', blockNumber]) || { + blocks: rangeRight(minBlock, maxBlock + 1) + .map((blockNumber) => find(result.blocks, ['blockNumber', blockNumber]) || { blockNumber, chainBlockHtml: placeHolderBlock(blockNumber) }) @@ -194,7 +198,7 @@ const elements = { const container = $el[0] if (state.blocksLoading === false) { - const blocks = _.map(state.blocks, ({ chainBlockHtml }) => $(chainBlockHtml)[0]) + const blocks = map(state.blocks, ({ chainBlockHtml }) => $(chainBlockHtml)[0]) listMorph(container, blocks, { key: 'dataset.blockNumber', horizontal: true }) } } @@ -234,7 +238,7 @@ const elements = { render ($el, state, oldState) { if (oldState.transactions === state.transactions) return const container = $el[0] - const newElements = _.map(state.transactions, ({ transactionHtml }) => $(transactionHtml)[0]) + const newElements = map(state.transactions, ({ transactionHtml }) => $(transactionHtml)[0]) listMorph(container, newElements, { key: 'dataset.identifierHash' }) } }, diff --git a/apps/block_scout_web/assets/js/pages/pending_transactions.js b/apps/block_scout_web/assets/js/pages/pending_transactions.js index 1cbb5d47d0..711f7c4343 100644 --- a/apps/block_scout_web/assets/js/pages/pending_transactions.js +++ b/apps/block_scout_web/assets/js/pages/pending_transactions.js @@ -1,5 +1,5 @@ import $ from 'jquery' -import _ from 'lodash' +import omit from 'lodash/omit' import humps from 'humps' import numeral from 'numeral' import socket from '../socket' @@ -20,7 +20,7 @@ export const initialState = { export function reducer (state = initialState, action) { switch (action.type) { case 'ELEMENTS_LOAD': { - return Object.assign({}, state, _.omit(action, 'type')) + return Object.assign({}, state, omit(action, 'type')) } case 'CHANNEL_DISCONNECTED': { return Object.assign({}, state, { diff --git a/apps/block_scout_web/assets/js/pages/transaction.js b/apps/block_scout_web/assets/js/pages/transaction.js index cd247d530a..139d1f527c 100644 --- a/apps/block_scout_web/assets/js/pages/transaction.js +++ b/apps/block_scout_web/assets/js/pages/transaction.js @@ -1,5 +1,5 @@ import $ from 'jquery' -import _ from 'lodash' +import omit from 'lodash/omit' import humps from 'humps' import numeral from 'numeral' import socket from '../socket' @@ -13,7 +13,7 @@ export const initialState = { export function reducer (state = initialState, action) { switch (action.type) { case 'ELEMENTS_LOAD': { - return Object.assign({}, state, _.omit(action, 'type')) + return Object.assign({}, state, omit(action, 'type')) } case 'RECEIVED_NEW_BLOCK': { if ((action.msg.blockNumber - state.blockNumber) > state.confirmations) { diff --git a/apps/block_scout_web/assets/js/pages/transactions.js b/apps/block_scout_web/assets/js/pages/transactions.js index a6c038166b..f528f760c4 100644 --- a/apps/block_scout_web/assets/js/pages/transactions.js +++ b/apps/block_scout_web/assets/js/pages/transactions.js @@ -1,5 +1,5 @@ import $ from 'jquery' -import _ from 'lodash' +import omit from 'lodash/omit' import humps from 'humps' import numeral from 'numeral' import socket from '../socket' @@ -18,7 +18,7 @@ export const initialState = { export function reducer (state = initialState, action) { switch (action.type) { case 'ELEMENTS_LOAD': { - return Object.assign({}, state, _.omit(action, 'type')) + return Object.assign({}, state, omit(action, 'type')) } case 'CHANNEL_DISCONNECTED': { return Object.assign({}, state, { diff --git a/apps/block_scout_web/assets/js/pages/verification_form.js b/apps/block_scout_web/assets/js/pages/verification_form.js index 619a8e1326..10504c144c 100644 --- a/apps/block_scout_web/assets/js/pages/verification_form.js +++ b/apps/block_scout_web/assets/js/pages/verification_form.js @@ -1,5 +1,5 @@ import $ from 'jquery' -import _ from 'lodash' +import omit from 'lodash/omit' import URI from 'urijs' import humps from 'humps' import { subscribeChannel } from '../socket' @@ -15,7 +15,7 @@ export function reducer (state = initialState, action) { switch (action.type) { case 'PAGE_LOAD': case 'ELEMENTS_LOAD': { - return Object.assign({}, state, _.omit(action, 'type')) + return Object.assign({}, state, omit(action, 'type')) } case 'CHANNEL_DISCONNECTED': { if (state.beyondPageOne) return state diff --git a/apps/block_scout_web/assets/webpack.config.js b/apps/block_scout_web/assets/webpack.config.js index 08a2f410e7..d7d504678d 100644 --- a/apps/block_scout_web/assets/webpack.config.js +++ b/apps/block_scout_web/assets/webpack.config.js @@ -1,7 +1,7 @@ const path = require('path'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin'); -const { ContextReplacementPlugin } = require('webpack') +const { ContextReplacementPlugin } = require('webpack'); const glob = require("glob"); function transpileViewScript(file) { From a31447d23b120101d690d55732ba18bf6c6b20fc Mon Sep 17 00:00:00 2001 From: maxgrapps Date: Sat, 20 Jul 2019 13:53:23 +0300 Subject: [PATCH 17/24] new block loading animation --- .../assets/css/components/_tile.scss | 168 +++++++++++++++++- .../templates/chain/_block.html.eex | 167 +++++++++++++++++ apps/block_scout_web/priv/gettext/default.pot | 6 +- 3 files changed, 335 insertions(+), 6 deletions(-) diff --git a/apps/block_scout_web/assets/css/components/_tile.scss b/apps/block_scout_web/assets/css/components/_tile.scss index 8965715727..dbfd117875 100644 --- a/apps/block_scout_web/assets/css/components/_tile.scss +++ b/apps/block_scout_web/assets/css/components/_tile.scss @@ -339,9 +339,6 @@ $tile-body-a-color: #5959d8 !default; padding-left: 6px; padding-right: 6px; } - .tile-type-block { - overflow: hidden; - } } .row { @include media-breakpoint-down(lg) { @@ -351,3 +348,168 @@ $tile-body-a-color: #5959d8 !default; } } } + +// Loading Animation + +@keyframes playBlockLoadingAnimation { + 0%, 90% { + opacity: 1; + } + 100% { + opacity: 0; + } +} + +[data-selector="chain-block-list"] { + .col-lg-3:first-child { + .tile-type-block-animation { + animation: playBlockLoadingAnimation 2.1s linear forwards; + } + } +} + +.fade-up-blocks-chain { + .tile-type-block { + position: relative; + } + .tile-type-block-animation { + opacity: 0; + position: absolute; + top: -1px; + left: -4px; + width: calc(100% + 5px); + height: calc(100% + 2px); + background-color: #F6F7F9; + border-radius: 4px; + overflow: hidden; + transition: .24s ease-out; + border-top: 1px solid #dee2e6; + border-right: 1px solid #dee2e6; + border-bottom: 1px solid #dee2e6; + pointer-events: none; + .tile-type-line-up { + position: absolute; + bottom: -1px; + left: 0; + height: calc(100% + 2px); + width: 4px; + background-color: $tile-type-block-color; + transform: scaleY(0); + transform-origin: center bottom; + animation: blockLoaderLine 2s linear forwards; + z-index: 2; + } + &:after { + content: ""; + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: 1px; + background-color: #dee2e6; + } + } +} + +.cube-animation-title { + font-size: 12px; + color: #a3a9b5; + position: absolute; + bottom: 10px; + left: 50%; + transform: translateX(-50%); +} + +.fade-up-blocks-chain:first-child { + .tile-type-block-animation { + opacity: 1; + } +} + +@keyframes blockLoaderLine { + 0% { + transform: scaleY(0); + } + 100% { + transform: scaleY(1); + } +} + +$cube-bezier: cubic-bezier(.25,.8,.25,1); +$cube-quantity: 5; + +.cube-animation-wrapper { + width: 560px; + height: 290px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%) scale(0.26); + svg { + width: 50px; + margin-top: -29px; + + .side { + fill: $tile-type-block-color; + opacity: 1; + + &:nth-of-type(2) { + fill: lighten($tile-type-block-color, 30); + opacity: 0.5; + } + + &:nth-of-type(3) { + fill: lighten($tile-type-block-color, 80); + opacity: 0.5; + } + } + } + + @while $cube-quantity > 0 { + .cube-animation-row:nth-of-type(#{$cube-quantity}) { + left: 25px * $cube-quantity; + top: 15px * $cube-quantity; + } + .cube-animation-column:nth-of-type(#{$cube-quantity}) { + position: relative; + top: 14px * $cube-quantity; + left: 25px * $cube-quantity; + } + .cube-animation-column:nth-of-type(#{$cube-quantity}) svg { + transform: translate3d(0,0,0); + animation: shrink-expand 2.8s $cube-bezier forwards; + animation-delay: -0.16s * $cube-quantity; + } + + $cube-quantity: $cube-quantity - 1; + } +} + +.cube-animation-center { + position: absolute; + top: 6%; + left: 20%; +} + +.cube-animation-row { + display: flex; + flex-direction: row-reverse; + position: absolute; +} + +.cube-animation-column { + display: flex; + flex-direction: column-reverse; +} + +@keyframes shrink-expand { + 0% { + transform: scale(0); + } + 50% { + transform: scale(1); + } + 100% { + transform: scale(0); + } +} \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/chain/_block.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/chain/_block.html.eex index 706d18ec47..729df59a3c 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/chain/_block.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/chain/_block.html.eex @@ -1,5 +1,172 @@
+
+
+ Block Validated, processing... +
+
+
+
+ + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+
<%= link( @block, class: "tile-title", diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index 61d7465f49..b8e56412a6 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -34,7 +34,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:21 -#: lib/block_scout_web/templates/chain/_block.html.eex:11 +#: lib/block_scout_web/templates/chain/_block.html.eex:178 msgid "%{count} Transactions" msgstr "" @@ -522,7 +522,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/block/_tile.html.eex:38 #: lib/block_scout_web/templates/block/overview.html.eex:121 -#: lib/block_scout_web/templates/chain/_block.html.eex:15 +#: lib/block_scout_web/templates/chain/_block.html.eex:182 msgid "Miner" msgstr "" @@ -1040,7 +1040,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/block/_tile.html.eex:47 -#: lib/block_scout_web/templates/chain/_block.html.eex:23 +#: lib/block_scout_web/templates/chain/_block.html.eex:190 #: lib/block_scout_web/views/internal_transaction_view.ex:27 msgid "Reward" msgstr "" From b0984ce343afab8b37a500a1d3c2e5e0b59750be Mon Sep 17 00:00:00 2001 From: maxgrapps <50101080+maxgrapps@users.noreply.github.com> Date: Sat, 20 Jul 2019 13:55:34 +0300 Subject: [PATCH 18/24] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9c8934f3d..5e1e9e2dc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - [#2294](https://github.com/poanetwork/blockscout/pull/2294) - add healthy block period checking endpoint ### Fixes +- [#2395](https://github.com/poanetwork/blockscout/pull/2395) - new block loading animation - [#2378](https://github.com/poanetwork/blockscout/pull/2378) - Page performance: exclude moment.js localization files except EN, remove unused css - [#2368](https://github.com/poanetwork/blockscout/pull/2368) - add two columns of smart contract info - [#2375](https://github.com/poanetwork/blockscout/pull/2375) - Update created_contract_code_indexed_at on transaction import conflict From a78bf7923e7c4ccdb2d9bbc7de20d95f861bda21 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 22 Jul 2019 10:40:52 +0300 Subject: [PATCH 19/24] use variable names as in json rpc --- .../api/rpc/eth_controller_test.exs | 6 ++-- apps/explorer/lib/explorer/eth_rpc.ex | 33 +++++++++++++------ 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs index d611d519ad..3ec07ee446 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs @@ -148,9 +148,9 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do {last_log_index, ""} = Integer.parse(List.last(response["result"])["logIndex"], 16) next_page_params = %{ - "block_number" => transaction.block_number, - "transaction_index" => transaction.index, - "log_index" => last_log_index + "blockNumber" => Integer.to_string(transaction.block_number, 16), + "transactionIndex" => transaction.index, + "logIndex" => "#{last_log_index}" } new_params = diff --git a/apps/explorer/lib/explorer/eth_rpc.ex b/apps/explorer/lib/explorer/eth_rpc.ex index 31e711d9fa..fa97a75561 100644 --- a/apps/explorer/lib/explorer/eth_rpc.ex +++ b/apps/explorer/lib/explorer/eth_rpc.ex @@ -228,17 +228,21 @@ defmodule Explorer.EthRPC do defp paging_options(%{ "paging_options" => %{ - "log_index" => log_index, - "transaction_index" => transaction_index, - "block_number" => block_number + "logIndex" => log_index, + "transactionIndex" => transaction_index, + "blockNumber" => block_number } - }) do - {:ok, - %{ - log_index: log_index, - transaction_index: transaction_index, - block_number: block_number - }} + }) + when is_integer(transaction_index) do + with {:ok, parsed_block_number} <- to_block_number(block_number), + {parsed_log_index, ""} <- Integer.parse(log_index) do + {:ok, + %{ + log_index: parsed_log_index, + transaction_index: transaction_index, + block_number: parsed_block_number + }} + end end defp paging_options(_), do: {:ok, nil} @@ -275,6 +279,15 @@ defmodule Explorer.EthRPC do defp to_block_number(_, _, _), do: {:error, "invalid block number"} + defp to_block_number(number) when is_bitstring(number) do + case Integer.parse(number, 16) do + {integer, ""} -> {:ok, integer} + _ -> {:error, "invalid block number"} + end + end + + defp to_block_number(_), do: {:error, "invalid block number"} + defp max_non_consensus_block_number(max) do case Chain.max_non_consensus_block_number(max) do {:ok, number} -> number From 993816e19e8f266a51083d257dfdc07cc1d7e3f5 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 22 Jul 2019 10:55:55 +0300 Subject: [PATCH 20/24] parse log index from hex --- .../controllers/api/rpc/eth_controller_test.exs | 2 +- apps/explorer/lib/explorer/eth_rpc.ex | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs index 3ec07ee446..4b43909ac4 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs @@ -150,7 +150,7 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do next_page_params = %{ "blockNumber" => Integer.to_string(transaction.block_number, 16), "transactionIndex" => transaction.index, - "logIndex" => "#{last_log_index}" + "logIndex" => Integer.to_string(last_log_index, 16) } new_params = diff --git a/apps/explorer/lib/explorer/eth_rpc.ex b/apps/explorer/lib/explorer/eth_rpc.ex index fa97a75561..6470e1b2af 100644 --- a/apps/explorer/lib/explorer/eth_rpc.ex +++ b/apps/explorer/lib/explorer/eth_rpc.ex @@ -234,8 +234,8 @@ defmodule Explorer.EthRPC do } }) when is_integer(transaction_index) do - with {:ok, parsed_block_number} <- to_block_number(block_number), - {parsed_log_index, ""} <- Integer.parse(log_index) do + with {:ok, parsed_block_number} <- to_number(block_number, "invalid block number"), + {:ok, parsed_log_index} <- to_number(log_index, "invalid log index") do {:ok, %{ log_index: parsed_log_index, @@ -279,14 +279,14 @@ defmodule Explorer.EthRPC do defp to_block_number(_, _, _), do: {:error, "invalid block number"} - defp to_block_number(number) when is_bitstring(number) do + defp to_number(number, error_message) when is_bitstring(number) do case Integer.parse(number, 16) do {integer, ""} -> {:ok, integer} - _ -> {:error, "invalid block number"} + _ -> {:error, error_message} end end - defp to_block_number(_), do: {:error, "invalid block number"} + defp to_number(_, error_message), do: {:error, error_message} defp max_non_consensus_block_number(max) do case Chain.max_non_consensus_block_number(max) do From 4eb63ebb6922314e20fb9f6b21bf6e38c0683171 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 22 Jul 2019 10:57:46 +0300 Subject: [PATCH 21/24] add new eth_getLogs example --- apps/explorer/lib/explorer/eth_rpc.ex | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/eth_rpc.ex b/apps/explorer/lib/explorer/eth_rpc.ex index 6470e1b2af..722a21e819 100644 --- a/apps/explorer/lib/explorer/eth_rpc.ex +++ b/apps/explorer/lib/explorer/eth_rpc.ex @@ -26,7 +26,13 @@ defmodule Explorer.EthRPC do For this reason, you can use pagination options to request the next page. Pagination options params: {"block_number": 1, "transaction_index": 0, "log_index": 1} which include parameters from the last log received from the previous request. """, example: """ - {"id": 0, "jsonrpc": "2.0", "method": "eth_getLogs", "params": [{"address": "0x0000000000000000000000000000000000000026","topics": ["0x01"]}]} + {"id": 0, "jsonrpc": "2.0", "method": "eth_getLogs", + "params": [ + {"address": "0xc78Be425090Dbd437532594D12267C5934Cc6c6f", + "paging_options": {"logIndex": "3D", "blockNumber": "6423AC", "transactionIndex": 53}, + "fromBlock": "earliest", + "toBlock": "latest", + "topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]}]} """ } } From 3ba1057355080bd7be79d241e9092862564f2c1f Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 22 Jul 2019 11:06:23 +0300 Subject: [PATCH 22/24] update note --- apps/explorer/lib/explorer/eth_rpc.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/eth_rpc.ex b/apps/explorer/lib/explorer/eth_rpc.ex index 722a21e819..e0864ac279 100644 --- a/apps/explorer/lib/explorer/eth_rpc.ex +++ b/apps/explorer/lib/explorer/eth_rpc.ex @@ -23,7 +23,7 @@ defmodule Explorer.EthRPC do action: :eth_get_logs, notes: """ Will never return more than 1000 log entries.\n - For this reason, you can use pagination options to request the next page. Pagination options params: {"block_number": 1, "transaction_index": 0, "log_index": 1} which include parameters from the last log received from the previous request. + For this reason, you can use pagination options to request the next page. Pagination options params: {"logIndex": "3D", "blockNumber": "6423AC", "transactionIndex": 53} which include parameters from the last log received from the previous request. These three parameters are required for pagination. """, example: """ {"id": 0, "jsonrpc": "2.0", "method": "eth_getLogs", From a7fd2883bff229e5ef6d32e603d1d0ab446de234 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 22 Jul 2019 12:13:19 +0300 Subject: [PATCH 23/24] show only one decoded candidate --- apps/explorer/lib/explorer/chain/transaction.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index 3b109504eb..78d162fa4e 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -416,7 +416,8 @@ defmodule Explorer.Chain.Transaction do candidates_query = from( contract_method in ContractMethod, - where: contract_method.identifier == ^method_id + where: contract_method.identifier == ^method_id, + limit: 1 ) candidates = From a5b014da77fdbb4d5543eeda55fd19169889f71a Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 22 Jul 2019 12:15:23 +0300 Subject: [PATCH 24/24] add CHANGELOG entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9c8934f3d..5735ca0862 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - [#2294](https://github.com/poanetwork/blockscout/pull/2294) - add healthy block period checking endpoint ### Fixes +- [#2398](https://github.com/poanetwork/blockscout/pull/2398) - show only one decoded candidate - [#2378](https://github.com/poanetwork/blockscout/pull/2378) - Page performance: exclude moment.js localization files except EN, remove unused css - [#2368](https://github.com/poanetwork/blockscout/pull/2368) - add two columns of smart contract info - [#2375](https://github.com/poanetwork/blockscout/pull/2375) - Update created_contract_code_indexed_at on transaction import conflict