diff --git a/.dialyzer-ignore b/.dialyzer-ignore index 11bb3be729..53e3554c6e 100644 --- a/.dialyzer-ignore +++ b/.dialyzer-ignore @@ -4,4 +4,4 @@ apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex:400: Function timestamp_to_datetime/1 has no local return apps/explorer/lib/explorer/repo/prometheus_logger.ex:8: Function microseconds_time/1 has no local return apps/explorer/lib/explorer/repo/prometheus_logger.ex:8: The call 'Elixir.System':convert_time_unit(__@1::any(),'native','microseconds') breaks the contract (integer(),time_unit() | 'native',time_unit() | 'native') -> integer() -apps/block_scout_web/lib/block_scout_web/views/layout_view.ex:162: The call 'Elixir.Poison.Parser':'parse!'(any(),#{'keys':='atoms!'}) will never return since the success typing is (binary() | maybe_improper_list(binary() | maybe_improper_list(any(),binary() | []) | byte(),binary() | []),[{atom(),_}]) -> 'false' | 'nil' | 'true' | binary() | ['false' | 'nil' | 'true' | binary() | [any()] | number() | map()] | number() | map() and the contract is (iodata(),'Elixir.Keyword':t()) -> t() \ No newline at end of file +apps/block_scout_web/lib/block_scout_web/views/layout_view.ex:174: The call 'Elixir.Poison.Parser':'parse!'(any(),#{'keys':='atoms!'}) will never return since the success typing is (binary() | maybe_improper_list(binary() | maybe_improper_list(any(),binary() | []) | byte(),binary() | []),[{atom(),_}]) -> 'false' | 'nil' | 'true' | binary() | ['false' | 'nil' | 'true' | binary() | [any()] | number() | map()] | number() | map() and the contract is (iodata(),'Elixir.Keyword':t()) -> t() \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 901f2d19ed..b8c086b1b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## Current ### Features +- [#1963](https://github.com/poanetwork/blockscout/pull/1963) - added rinkeby theme and rinkeby logo - [#1959](https://github.com/poanetwork/blockscout/pull/1959) - added goerli theme and goerli logo - [#1928](https://github.com/poanetwork/blockscout/pull/1928) - pagination styles were updated - [#1948](https://github.com/poanetwork/blockscout/pull/1948) - added ropsten theme and ropsten logo @@ -21,6 +22,8 @@ - [#1920](https://github.com/poanetwork/blockscout/pull/1920) - fix: remove source code fields from list endpoint - [#1876](https://github.com/poanetwork/blockscout/pull/1876) - async calculate a count of blocks - [#1941](https://github.com/poanetwork/blockscout/pull/1941) - feat: add on demand fetching and stale attr to rpc +- [#1956](https://github.com/poanetwork/blockscout/pull/1956) - add logs tab to address +- [#1933](https://github.com/poanetwork/blockscout/pull/1933) - add eth_BlockNumber json rpc method - [#1952](https://github.com/poanetwork/blockscout/pull/1952) - feat: exclude empty contracts by default ### Fixes @@ -53,6 +56,7 @@ - [#1837](https://github.com/poanetwork/blockscout/pull/1837) - Add -f flag to clear_build.sh script delete static folder - [#1900](https://github.com/poanetwork/blockscout/pull/1900) - SUPPORTED_CHAINS ENV var - [#1892](https://github.com/poanetwork/blockscout/pull/1892) - Remove temporary worker modules +- [#1958](https://github.com/poanetwork/blockscout/pull/1958) - Default value for release link env var ## 1.3.10-beta diff --git a/README.md b/README.md index 47dc73ce51..1c86dfc34a 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Currently available block explorers (i.e. Etherscan and Etherchain) are closed s | [Callisto](https://blockscout.com/callisto/mainnet) | [Kovan Testnet](https://blockscout.com/eth/kovan) | [Ether-1](https://blocks.ether1.wattpool.net/) | | [Ethereum Classic](https://blockscout.com/etc/mainnet) | [POA Sokol Testnet](https://blockscout.com/poa/sokol) | [Fuse Network](https://explorer.fuse.io/) | | [Ethereum Mainnet](https://blockscout.com/eth/mainnet) | [Rinkeby Testnet](https://blockscout.com/eth/rinkeby) | [Oasis Labs](https://blockexplorer.oasiscloud.io/) | -| [POA Core Network](https://blockscout.com/poa/core) | [Ropsten Testnet](https://blockscout.com/eth/ropsten) | [Petrichor](https://explorer.petrichor-dev.com/) | +| [POA Core Network](https://blockscout.com/poa/core) | [Ropsten Testnet](https://blockscout.com/eth/ropsten) | [Petrichor](https://explorer.petrachor.com/) | | [RSK](https://blockscout.com/rsk/mainnet) | | [PIRL](http://pirl.es/) | | [xDai Chain](https://blockscout.com/poa/dai) | | [SafeChain](https://explorer.safechain.io) | | | | [SpringChain](https://explorer.springrole.com/) | diff --git a/apps/block_scout_web/assets/css/theme/_rinkeby_variables.scss b/apps/block_scout_web/assets/css/theme/_rinkeby_variables.scss index 0d6eb549c4..1cc6f3d1c4 100644 --- a/apps/block_scout_web/assets/css/theme/_rinkeby_variables.scss +++ b/apps/block_scout_web/assets/css/theme/_rinkeby_variables.scss @@ -1,3 +1,50 @@ -$primary: #2a3f54; -$secondary: #39c4a9; -$tertiary: #39c4a9; +// general +$primary: #193653; +$secondary: #49a2ee; +$tertiary: #41c3a9; +$additional-font: #a3ceff; + +// footer +$footer-background-color: $primary; +$footer-title-color: #fff; +$footer-text-color: $additional-font; +$footer-item-disc-color: $additional-font; +.footer-logo { filter: brightness(0) invert(1); } + +// dashboard +$dashboard-line-color-price: $tertiary; // price left border + +$dashboard-banner-chart-legend-value-color: $additional-font; // chart labels + +$dashboard-stats-item-value-color: $additional-font; // stat values + +$dashboard-stats-item-border-color: $tertiary; // stat border + +$dashboard-banner-gradient-start: $primary; // gradient begin + +$dashboard-banner-gradient-end: lighten($primary, 5); // gradient end + +$dashboard-banner-network-plain-container-background-color: #244468; // stats bg + + +// navigation +.navbar { box-shadow: 0px 0px 30px 0px rgba(21, 53, 80, 0.12); } // header shadow +$header-icon-border-color-hover: $tertiary; // top border on hover +$header-icon-color-hover: $tertiary; // nav icon on hover +.dropdown-item:hover, .dropdown-item:focus { background-color: $tertiary !important; } // dropdown item on hover + +// buttons +$btn-line-bg: #fff; // button bg +$btn-line-color: $tertiary; // button border and font color && hover bg color +$btn-copy-color: $tertiary; // btn copy +$btn-qr-color: $tertiary; // btn qr-code + +//links & tile +.tile a { color: $tertiary !important; } // links color for badges +.tile-type-block { + border-left: 4px solid $tertiary; +} // tab active bg + +// card +$card-background-1: $secondary; +$card-tab-active: $secondary; diff --git a/apps/block_scout_web/assets/static/images/rinkeby_logo.svg b/apps/block_scout_web/assets/static/images/rinkeby_logo.svg index 5735453929..4659ef91be 100644 --- a/apps/block_scout_web/assets/static/images/rinkeby_logo.svg +++ b/apps/block_scout_web/assets/static/images/rinkeby_logo.svg @@ -1,36 +1 @@ - - - - - rinkeby_logo - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/apps/block_scout_web/lib/block_scout_web/chain.ex b/apps/block_scout_web/lib/block_scout_web/chain.ex index cabac4ae02..9e82c30023 100644 --- a/apps/block_scout_web/lib/block_scout_web/chain.ex +++ b/apps/block_scout_web/lib/block_scout_web/chain.ex @@ -205,8 +205,12 @@ defmodule BlockScoutWeb.Chain do %{"block_number" => block_number, "transaction_index" => transaction_index, "index" => index} end - defp paging_params(%Log{index: index}) do - %{"index" => index} + defp paging_params(%Log{index: index} = log) do + if Ecto.assoc_loaded?(log.transaction) do + %{"block_number" => log.transaction.block_number, "transaction_index" => log.transaction.index, "index" => index} + else + %{"index" => index} + end end defp paging_params(%Transaction{block_number: nil, inserted_at: inserted_at, hash: hash}) do diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex new file mode 100644 index 0000000000..f79d9aa08d --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex @@ -0,0 +1,46 @@ +defmodule BlockScoutWeb.AddressLogsController do + @moduledoc """ + Manages events logs tab. + """ + + import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1] + import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] + + alias Explorer.{Chain, Market} + alias Explorer.ExchangeRates.Token + alias Indexer.Fetcher.CoinBalanceOnDemand + + use BlockScoutWeb, :controller + + def index(conn, %{"address_id" => address_hash_string} = params) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, address} <- Chain.hash_to_address(address_hash) do + logs_plus_one = Chain.address_to_logs(address, paging_options(params)) + {results, next_page} = split_list_by_page(logs_plus_one) + + next_page_url = + case next_page_params(next_page, results, params) do + nil -> + nil + + next_page_params -> + address_logs_path(conn, :index, address, next_page_params) + end + + render( + conn, + "index.html", + address: address, + logs: results, + coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), + exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + transaction_count: transaction_count(address), + validation_count: validation_count(address), + next_page_url: next_page_url + ) + else + _ -> + not_found(conn) + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex index e6991bf1af..8beb8ea8ea 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex @@ -3,6 +3,7 @@ defmodule BlockScoutWeb.API.RPC.BlockController do alias BlockScoutWeb.Chain, as: ChainWeb alias Explorer.Chain + alias Explorer.Chain.BlockNumberCache def getblockreward(conn, params) do with {:block_param, {:ok, unsafe_block_number}} <- {:block_param, Map.fetch(params, "blockno")}, @@ -23,4 +24,11 @@ defmodule BlockScoutWeb.API.RPC.BlockController do render(conn, :error, error: "Block does not exist") end end + + def eth_block_number(conn, params) do + id = Map.get(params, "id", 1) + max_block_number = BlockNumberCache.max_number() + + render(conn, :eth_block_number, number: max_block_number, id: id) + end end diff --git a/apps/block_scout_web/lib/block_scout_web/etherscan.ex b/apps/block_scout_web/lib/block_scout_web/etherscan.ex index a657ff8fd2..5f113a65df 100644 --- a/apps/block_scout_web/lib/block_scout_web/etherscan.ex +++ b/apps/block_scout_web/lib/block_scout_web/etherscan.ex @@ -279,6 +279,12 @@ defmodule BlockScoutWeb.Etherscan do "result" => nil } + @block_eth_block_number_example_value %{ + "jsonrpc" => "2.0", + "result" => "767969", + "id" => 1 + } + @contract_listcontracts_example_value %{ "status" => "1", "message" => "OK", @@ -476,11 +482,26 @@ defmodule BlockScoutWeb.Etherscan do enum_interpretation: %{"0" => "error", "1" => "ok"} } + @jsonrpc_version_type %{ + type: "string", + example: ~s("2.0") + } + @message_type %{ type: "string", example: ~s("OK") } + @hex_number_type %{ + type: "string", + example: ~s("767969") + } + + @id_type %{ + type: "string", + example: ~s("1") + } + @wei_type %{ type: "wei", definition: &__MODULE__.wei_type_definition/1, @@ -1737,6 +1758,35 @@ defmodule BlockScoutWeb.Etherscan do ] } + @block_eth_block_number_action %{ + name: "eth_block_number", + description: "Mimics Ethereum JSON RPC's eth_blockNumber. Returns the lastest block number", + required_params: [], + optional_params: [ + %{ + key: "id", + placeholder: "request id", + type: "integer", + description: "A nonnegative integer that represents the json rpc request id." + } + ], + responses: [ + %{ + code: "200", + description: "successful request", + example_value: Jason.encode!(@block_eth_block_number_example_value), + model: %{ + name: "Result", + fields: %{ + jsonrpc: @jsonrpc_version_type, + id: @id_type, + result: @hex_number_type + } + } + } + ] + } + @block_getblockreward_action %{ name: "getblockreward", description: "Get block reward by block number.", @@ -2171,7 +2221,7 @@ defmodule BlockScoutWeb.Etherscan do @block_module %{ name: "block", - actions: [@block_getblockreward_action] + actions: [@block_getblockreward_action, @block_eth_block_number_action] } @contract_module %{ diff --git a/apps/block_scout_web/lib/block_scout_web/router.ex b/apps/block_scout_web/lib/block_scout_web/router.ex index c0e975793f..b2f639d763 100644 --- a/apps/block_scout_web/lib/block_scout_web/router.ex +++ b/apps/block_scout_web/lib/block_scout_web/router.ex @@ -147,6 +147,13 @@ defmodule BlockScoutWeb.Router do as: :decompiled_contract ) + resources( + "/logs", + AddressLogsController, + only: [:index], + as: :logs + ) + resources( "/contract_verifications", AddressContractVerificationController, diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_tabs.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_tabs.html.eex index bbf27c8f96..8ed8d4e028 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address/_tabs.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_tabs.html.eex @@ -22,6 +22,11 @@ "data-test": "coin_balance_tab_link", to: address_coin_balance_path(@conn, :index, @address.hash) ) %> + <%= link( + gettext("Logs"), + class: "card-tab #{tab_status("logs", @conn.request_path)}", + to: address_logs_path(@conn, :index, @address.hash) + ) %> <%= if BlockScoutWeb.AddressView.validator?(@validation_count) do %> <%= link( gettext("Blocks Validated"), @@ -55,4 +60,4 @@ class: "card-tab #{tab_status("read_contract", @conn.request_path)}") %> <% end %> - \ No newline at end of file + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_logs/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_logs/index.html.eex new file mode 100644 index 0000000000..22f446924f --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_logs/index.html.eex @@ -0,0 +1,82 @@ +
+ <%= render BlockScoutWeb.AddressView, "overview.html", assigns %> +
+ <%= render BlockScoutWeb.AddressView, "_tabs.html", assigns %> + +
+ +

<%= gettext "Logs" %>

+ + <%= if @next_page_url do %> + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", cur_page_number: "1", show_pagination_limit: true, next_page_path: @next_page_url %> + <% end %> + + <%= if !@next_page_url do %> + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", cur_page_number: "1", show_pagination_limit: true %> + <% end %> + + <%= if Enum.count(@logs) > 0 do %> + <%= for log <- @logs do %> +
+
+
<%= gettext "Transaction" %>
+
+

+ <%= link( + log.transaction, + to: transaction_path(@conn, :show, log.transaction), + "data-test": "log_address_link", + "data-address-hash": log.transaction + ) %> +

+
+
<%= gettext "Topics" %>
+
+
+ <%= unless is_nil(log.first_topic) do %> +
+ [0] + <%= log.first_topic %> +
+ <% end %> + <%= unless is_nil(log.second_topic) do %> +
+ [1] + <%= log.second_topic %> +
+ <% end %> + <%= unless is_nil(log.third_topic) do %> +
+ [2] + <%= log.third_topic %> +
+ <% end %> + <%= unless is_nil(log.fourth_topic) do %> +
+ [3] + <%= log.fourth_topic %> +
+ <% end %> +
+
+
+ <%= gettext "Data" %> +
+
+ <%= unless is_nil(log.data) do %> +
+ <%= log.data %> +
+ <% end %> +
+
+
+ <% end %> + <% else %> +
+ <%= gettext "There are no logs for this address." %> +
+ <% end %> +
+
+
diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_logs_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_logs_view.ex new file mode 100644 index 0000000000..7155e65206 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/address_logs_view.ex @@ -0,0 +1,3 @@ +defmodule BlockScoutWeb.AddressLogsView do + use BlockScoutWeb, :view +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex index 6ce72b90ae..7a6005d526 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex @@ -300,6 +300,7 @@ defmodule BlockScoutWeb.AddressView do defp tab_name(["read_contract"]), do: gettext("Read Contract") defp tab_name(["coin_balances"]), do: gettext("Coin Balance History") defp tab_name(["validations"]), do: gettext("Blocks Validated") + defp tab_name(["logs"]), do: gettext("Logs") def short_hash(%Address{hash: hash}) do << diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/block_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/block_view.ex index 941a064804..5c98794b2a 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/block_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/block_view.ex @@ -1,7 +1,7 @@ defmodule BlockScoutWeb.API.RPC.BlockView do use BlockScoutWeb, :view - alias BlockScoutWeb.API.RPC.RPCView + alias BlockScoutWeb.API.RPC.{EthRPCView, RPCView} alias Explorer.Chain.{Hash, Wei} def render("block_reward.json", %{block: block, reward: reward}) do @@ -22,7 +22,33 @@ defmodule BlockScoutWeb.API.RPC.BlockView do RPCView.render("show.json", data: data) end + def render("eth_block_number.json", %{number: number, id: id}) do + result = encode_quantity(number) + + EthRPCView.render("show.json", %{result: result, id: id}) + end + def render("error.json", %{error: error}) do RPCView.render("error.json", error: error) end + + defp encode_quantity(binary) when is_binary(binary) do + hex_binary = Base.encode16(binary, case: :lower) + + result = String.replace_leading(hex_binary, "0", "") + + final_result = if result == "", do: "0", else: result + + "0x#{final_result}" + end + + defp encode_quantity(value) when is_integer(value) do + value + |> :binary.encode_unsigned() + |> encode_quantity() + end + + defp encode_quantity(value) when is_nil(value) do + nil + end end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/eth_rpc_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/eth_rpc_view.ex new file mode 100644 index 0000000000..39eb5ae9d1 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/eth_rpc_view.ex @@ -0,0 +1,33 @@ +defmodule BlockScoutWeb.API.RPC.EthRPCView do + use BlockScoutWeb, :view + + defstruct [:result, :id, :error] + + def render("show.json", %{result: result, id: id}) do + %__MODULE__{ + result: result, + id: id + } + end + + def render("error.json", %{error: message, id: id}) do + %__MODULE__{ + error: message, + id: id + } + end + + defimpl Poison.Encoder, for: BlockScoutWeb.API.RPC.EthRPCView do + def encode(%BlockScoutWeb.API.RPC.EthRPCView{result: result, id: id, error: error}, _options) when is_nil(error) do + """ + {"jsonrpc":"2.0","result":"#{result}","id":#{id}} + """ + end + + def encode(%BlockScoutWeb.API.RPC.EthRPCView{id: id, error: error}, _options) do + """ + {"jsonrpc":"2.0","error": #{error},"id": #{id}} + """ + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex b/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex index 7a454a0e72..12df6d3f0a 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex @@ -142,10 +142,22 @@ defmodule BlockScoutWeb.LayoutView do end def release_link(version) do - release_link = Application.get_env(:block_scout_web, :release_link) + release_link_env_var = Application.get_env(:block_scout_web, :release_link) - if release_link == "" || release_link == nil do - version + release_link = + cond do + version == "" || version == nil -> + nil + + release_link_env_var == "" || release_link_env_var == nil -> + "https://github.com/poanetwork/blockscout/releases/tag/" <> version + + true -> + release_link_env_var + end + + if release_link == nil do + "" else html_escape({:safe, "#{version}"}) end diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index 7922c7d2f0..4917896dd7 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -187,7 +187,7 @@ msgid "Blocks Indexed" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/_tabs.html.eex:27 +#: lib/block_scout_web/templates/address/_tabs.html.eex:32 #: lib/block_scout_web/templates/address/overview.html.eex:95 #: lib/block_scout_web/templates/address_validation/index.html.eex:13 #: lib/block_scout_web/views/address_view.ex:302 @@ -215,7 +215,7 @@ msgid "Close" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/_tabs.html.eex:37 +#: lib/block_scout_web/templates/address/_tabs.html.eex:42 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:165 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187 #: lib/block_scout_web/views/address_view.ex:298 @@ -334,6 +334,7 @@ msgid "Curl" msgstr "" #, elixir-format +#: lib/block_scout_web/templates/address_logs/index.html.eex:63 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:18 #: lib/block_scout_web/templates/transaction_log/index.html.eex:67 #: lib/block_scout_web/templates/transaction_log/index.html.eex:133 @@ -505,8 +506,11 @@ msgid "Limit" msgstr "" #, elixir-format +#: lib/block_scout_web/templates/address/_tabs.html.eex:26 +#: lib/block_scout_web/templates/address_logs/index.html.eex:8 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:17 #: lib/block_scout_web/templates/transaction_log/index.html.eex:8 +#: lib/block_scout_web/views/address_view.ex:303 #: lib/block_scout_web/views/transaction_view.ex:340 msgid "Logs" msgstr "" @@ -682,7 +686,7 @@ msgid "Query" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/_tabs.html.eex:53 +#: lib/block_scout_web/templates/address/_tabs.html.eex:58 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:25 #: lib/block_scout_web/views/address_view.ex:300 #: lib/block_scout_web/views/tokens/overview_view.ex:37 @@ -874,6 +878,7 @@ msgid "Top Accounts - %{subnetwork} Explorer" msgstr "" #, elixir-format +#: lib/block_scout_web/templates/address_logs/index.html.eex:33 #: lib/block_scout_web/templates/transaction_log/index.html.eex:103 msgid "Topics" msgstr "" @@ -894,6 +899,7 @@ msgid "Total transactions" msgstr "" #, elixir-format +#: lib/block_scout_web/templates/address_logs/index.html.eex:22 #: lib/block_scout_web/views/transaction_view.ex:287 msgid "Transaction" msgstr "" @@ -1661,7 +1667,7 @@ msgid "Decompiled Code" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/_tabs.html.eex:47 +#: lib/block_scout_web/templates/address/_tabs.html.eex:52 msgid "Decompiled code" msgstr "" @@ -1745,3 +1751,8 @@ msgstr "" #: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:37 msgid "of" msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_logs/index.html.eex:77 +msgid "There are no logs for this address." +msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index 8925a95acf..6fc4e2ddec 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -187,7 +187,7 @@ msgid "Blocks Indexed" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/_tabs.html.eex:27 +#: lib/block_scout_web/templates/address/_tabs.html.eex:32 #: lib/block_scout_web/templates/address/overview.html.eex:95 #: lib/block_scout_web/templates/address_validation/index.html.eex:13 #: lib/block_scout_web/views/address_view.ex:302 @@ -215,7 +215,7 @@ msgid "Close" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/_tabs.html.eex:37 +#: lib/block_scout_web/templates/address/_tabs.html.eex:42 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:165 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187 #: lib/block_scout_web/views/address_view.ex:298 @@ -334,6 +334,7 @@ msgid "Curl" msgstr "" #, elixir-format +#: lib/block_scout_web/templates/address_logs/index.html.eex:63 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:18 #: lib/block_scout_web/templates/transaction_log/index.html.eex:67 #: lib/block_scout_web/templates/transaction_log/index.html.eex:133 @@ -505,8 +506,11 @@ msgid "Limit" msgstr "" #, elixir-format +#: lib/block_scout_web/templates/address/_tabs.html.eex:26 +#: lib/block_scout_web/templates/address_logs/index.html.eex:8 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:17 #: lib/block_scout_web/templates/transaction_log/index.html.eex:8 +#: lib/block_scout_web/views/address_view.ex:303 #: lib/block_scout_web/views/transaction_view.ex:340 msgid "Logs" msgstr "" @@ -682,7 +686,7 @@ msgid "Query" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/_tabs.html.eex:53 +#: lib/block_scout_web/templates/address/_tabs.html.eex:58 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:25 #: lib/block_scout_web/views/address_view.ex:300 #: lib/block_scout_web/views/tokens/overview_view.ex:37 @@ -874,6 +878,7 @@ msgid "Top Accounts - %{subnetwork} Explorer" msgstr "" #, elixir-format +#: lib/block_scout_web/templates/address_logs/index.html.eex:33 #: lib/block_scout_web/templates/transaction_log/index.html.eex:103 msgid "Topics" msgstr "" @@ -894,6 +899,7 @@ msgid "Total transactions" msgstr "" #, elixir-format +#: lib/block_scout_web/templates/address_logs/index.html.eex:22 #: lib/block_scout_web/views/transaction_view.ex:287 msgid "Transaction" msgstr "" @@ -1357,7 +1363,7 @@ msgid "Loading balances" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:23 +#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:22 #: lib/block_scout_web/templates/chain/show.html.eex:13 msgid "Loading chart" msgstr "" @@ -1368,7 +1374,7 @@ msgid "There is no coin history for this address." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:26 +#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:25 #: lib/block_scout_web/templates/chain/show.html.eex:16 msgid "There was a problem loading the chart." msgstr "" @@ -1661,7 +1667,7 @@ msgid "Decompiled Code" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/_tabs.html.eex:47 +#: lib/block_scout_web/templates/address/_tabs.html.eex:52 msgid "Decompiled code" msgstr "" @@ -1745,3 +1751,8 @@ msgstr "" #: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:37 msgid "of" msgstr "" + +#, elixir-format, fuzzy +#: lib/block_scout_web/templates/address_logs/index.html.eex:77 +msgid "There are no logs for this address." +msgstr "" diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs index b3b8f0b08b..993b25fcec 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs @@ -25,7 +25,6 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do start_supervised!(AddressesWithBalanceCounter) Application.put_env(:explorer, AverageBlockTime, enabled: true) - BlockNumberCache.setup(cache_period: 0) on_exit(fn -> Application.put_env(:explorer, AverageBlockTime, enabled: false) diff --git a/apps/block_scout_web/test/block_scout_web/views/layout_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/layout_view_test.exs index 5e3302ceec..5787159b14 100644 --- a/apps/block_scout_web/test/block_scout_web/views/layout_view_test.exs +++ b/apps/block_scout_web/test/block_scout_web/views/layout_view_test.exs @@ -62,16 +62,32 @@ defmodule BlockScoutWeb.LayoutViewTest do end describe "release_link/1" do - test "use the version when there is no release_link env configured for it" do + test "set empty string if no blockscout version configured" do + Application.put_env(:block_scout_web, :blockscout_version, nil) + + assert LayoutView.release_link(nil) == "" + end + + test "set empty string if blockscout version is empty string" do + Application.put_env(:block_scout_web, :blockscout_version, "") + + assert LayoutView.release_link("") == "" + end + + test "use the default value when there is no release_link env configured for it" do Application.put_env(:block_scout_web, :release_link, nil) - assert LayoutView.release_link("1.3.4") == "1.3.4" + assert LayoutView.release_link("v1.3.4-beta") == + {:safe, + ~s(v1.3.4-beta)} end - test "use the version when empty release_link env configured for it" do + test "use the default value when empty release_link env configured for it" do Application.put_env(:block_scout_web, :release_link, "") - assert LayoutView.release_link("1.3.4") == "1.3.4" + assert LayoutView.release_link("v1.3.4-beta") == + {:safe, + ~s(v1.3.4-beta)} end test "use the enviroment release link when it's configured" do @@ -81,9 +97,9 @@ defmodule BlockScoutWeb.LayoutViewTest do "https://github.com/poanetwork/blockscout/releases/tag/v1.3.4-beta" ) - assert LayoutView.release_link("1.3.4") == + assert LayoutView.release_link("v1.3.4-beta") == {:safe, - ~s(1.3.4)} + ~s(v1.3.4-beta)} end end diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index e6eb5c4813..eba5dd34cf 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -13,6 +13,8 @@ config :explorer, config :explorer, Explorer.Counters.AverageBlockTime, enabled: true +config :explorer, Explorer.Chain.BlockNumberCache, enabled: true + config :explorer, Explorer.ExchangeRates.Source.CoinMarketCap, pages: String.to_integer(System.get_env("COINMARKETCAP_PAGES") || "10") diff --git a/apps/explorer/config/test.exs b/apps/explorer/config/test.exs index 0a717d8b27..f27fc3b350 100644 --- a/apps/explorer/config/test.exs +++ b/apps/explorer/config/test.exs @@ -13,6 +13,8 @@ config :explorer, Explorer.Repo, config :explorer, Explorer.ExchangeRates, enabled: false, store: :ets +config :explorer, Explorer.Chain.BlockNumberCache, enabled: false + config :explorer, Explorer.KnownTokens, enabled: false, store: :ets config :explorer, Explorer.Counters.AverageBlockTime, enabled: false diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 59258a90d1..9f394b2561 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -279,6 +279,38 @@ defmodule Explorer.Chain do |> Enum.take(paging_options.page_size) end + @spec address_to_logs(Address.t(), [paging_options]) :: [ + Log.t() + ] + def address_to_logs( + %Address{hash: %Hash{byte_count: unquote(Hash.Address.byte_count())} = address_hash}, + options \\ [] + ) + when is_list(options) do + paging_options = Keyword.get(options, :paging_options) || %PagingOptions{page_size: 50} + + {block_number, transaction_index, log_index} = paging_options.key || {BlockNumberCache.max_number(), 0, 0} + + query = + from(log in Log, + inner_join: transaction in assoc(log, :transaction), + order_by: [desc: transaction.block_number, desc: transaction.index], + preload: [:transaction], + where: + log.address_hash == ^address_hash and + (transaction.block_number < ^block_number or + (transaction.block_number == ^block_number and transaction.index > ^transaction_index) or + (transaction.block_number == ^block_number and transaction.index == ^transaction_index and + log.index > ^log_index)), + limit: ^paging_options.page_size, + select: log + ) + + query + |> Repo.all() + |> Enum.take(paging_options.page_size) + end + @doc """ Finds all `t:Explorer.Chain.Transaction.t/0`s given the address_hash and the token contract address hash. diff --git a/apps/explorer/lib/explorer/chain/block_number_cache.ex b/apps/explorer/lib/explorer/chain/block_number_cache.ex index 28d8d83e9c..2f335c7a2c 100644 --- a/apps/explorer/lib/explorer/chain/block_number_cache.ex +++ b/apps/explorer/lib/explorer/chain/block_number_cache.ex @@ -6,13 +6,10 @@ defmodule Explorer.Chain.BlockNumberCache do alias Explorer.Chain @tab :block_number_cache - # 30 minutes - @cache_period 1_000 * 60 * 30 @key "min_max" - @opts_key "opts" - @spec setup(Keyword.t()) :: :ok - def setup(opts \\ []) do + @spec setup() :: :ok + def setup do if :ets.whereis(@tab) == :undefined do :ets.new(@tab, [ :set, @@ -22,7 +19,6 @@ defmodule Explorer.Chain.BlockNumberCache do ]) end - setup_opts(opts) update_cache() :ok @@ -41,15 +37,11 @@ defmodule Explorer.Chain.BlockNumberCache do end defp value(type) do - initial_cache = {_min, _max, old_current_time} = cached_values() - - {min, max, _current_time} = - if current_time() - old_current_time > cache_period() do - update_cache() - + {min, max} = + if Application.get_env(:explorer, __MODULE__)[:enabled] do cached_values() else - initial_cache + min_and_max_from_db() end case type do @@ -59,18 +51,29 @@ defmodule Explorer.Chain.BlockNumberCache do end end - defp update_cache do - current_time = current_time() - {min, max} = min_and_max_from_db() - tuple = {min, max, current_time} + @spec update(non_neg_integer()) :: boolean() + def update(number) do + {old_min, old_max} = cached_values() - :ets.insert(@tab, {@key, tuple}) + cond do + number > old_max -> + tuple = {old_min, number} + :ets.insert(@tab, {@key, tuple}) + + number < old_min -> + tuple = {number, old_max} + :ets.insert(@tab, {@key, tuple}) + + true -> + false + end end - defp setup_opts(opts) do - cache_period = opts[:cache_period] || @cache_period + defp update_cache do + {min, max} = min_and_max_from_db() + tuple = {min, max} - :ets.insert(@tab, {@opts_key, cache_period}) + :ets.insert(@tab, {@key, tuple}) end defp cached_values do @@ -79,22 +82,10 @@ defmodule Explorer.Chain.BlockNumberCache do cached_values end - defp cache_period do - [{_, cache_period}] = :ets.lookup(@tab, @opts_key) - - cache_period - end - defp min_and_max_from_db do Chain.fetch_min_and_max_block_numbers() rescue _e -> {0, 0} end - - defp current_time do - utc_now = DateTime.utc_now() - - DateTime.to_unix(utc_now, :millisecond) - end end diff --git a/apps/explorer/test/explorer/chain/block_number_cache_test.exs b/apps/explorer/test/explorer/chain/block_number_cache_test.exs index a33263293c..7b501a718b 100644 --- a/apps/explorer/test/explorer/chain/block_number_cache_test.exs +++ b/apps/explorer/test/explorer/chain/block_number_cache_test.exs @@ -3,6 +3,14 @@ defmodule Explorer.Chain.BlockNumberCacheTest do alias Explorer.Chain.BlockNumberCache + setup do + Application.put_env(:explorer, Explorer.Chain.BlockNumberCache, enabled: true) + + on_exit(fn -> + Application.put_env(:explorer, Explorer.Chain.BlockNumberCache, enabled: false) + end) + end + describe "max_number/1" do test "returns max number" do insert(:block, number: 5) @@ -11,33 +19,6 @@ defmodule Explorer.Chain.BlockNumberCacheTest do assert BlockNumberCache.max_number() == 5 end - - test "invalidates cache if period did pass" do - insert(:block, number: 5) - - BlockNumberCache.setup(cache_period: 2_000) - - assert BlockNumberCache.max_number() == 5 - - insert(:block, number: 10) - - Process.sleep(2_000) - - assert BlockNumberCache.max_number() == 10 - assert BlockNumberCache.min_number() == 5 - end - - test "does not invalidate cache if period time did not pass" do - insert(:block, number: 5) - - BlockNumberCache.setup(cache_period: 10_000) - - assert BlockNumberCache.max_number() == 5 - - insert(:block, number: 10) - - assert BlockNumberCache.max_number() == 5 - end end describe "min_number/1" do @@ -48,32 +29,31 @@ defmodule Explorer.Chain.BlockNumberCacheTest do assert BlockNumberCache.max_number() == 2 end + end - test "invalidates cache" do - insert(:block, number: 5) - - BlockNumberCache.setup(cache_period: 2_000) + describe "update/1" do + test "updates max number" do + insert(:block, number: 2) - assert BlockNumberCache.min_number() == 5 + BlockNumberCache.setup() - insert(:block, number: 2) + assert BlockNumberCache.max_number() == 2 - Process.sleep(2_000) + assert BlockNumberCache.update(3) - assert BlockNumberCache.min_number() == 2 - assert BlockNumberCache.max_number() == 5 + assert BlockNumberCache.max_number() == 3 end - test "does not invalidate cache if period time did not pass" do - insert(:block, number: 5) + test "updates min number" do + insert(:block, number: 2) - BlockNumberCache.setup(cache_period: 10_000) + BlockNumberCache.setup() - assert BlockNumberCache.max_number() == 5 + assert BlockNumberCache.min_number() == 2 - insert(:block, number: 2) + assert BlockNumberCache.update(1) - assert BlockNumberCache.max_number() == 5 + assert BlockNumberCache.min_number() == 1 end end end diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index a9b479c3bd..29f1b68c64 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -50,6 +50,51 @@ defmodule Explorer.ChainTest do end end + describe "address_to_logs/2" do + test "fetches logs" do + address = insert(:address) + + transaction1 = + :transaction + |> insert(to_address: address) + |> with_block() + + insert(:log, transaction: transaction1, index: 1, address: address) + + transaction2 = + :transaction + |> insert(from_address: address) + |> with_block() + + insert(:log, transaction: transaction2, index: 2, address: address) + + assert Enum.count(Chain.address_to_logs(address)) == 2 + end + + test "paginates logs" do + address = insert(:address) + + transaction = + :transaction + |> insert(to_address: address) + |> with_block() + + log1 = insert(:log, transaction: transaction, index: 1, address: address) + + 2..51 + |> Enum.map(fn index -> insert(:log, transaction: transaction, index: index, address: address) end) + |> Enum.map(& &1.index) + + paging_options1 = %PagingOptions{page_size: 1} + + [_log] = Chain.address_to_logs(address, paging_options: paging_options1) + + paging_options2 = %PagingOptions{page_size: 60, key: {transaction.block_number, transaction.index, log1.index}} + + assert Enum.count(Chain.address_to_logs(address, paging_options: paging_options2)) == 50 + end + end + describe "address_to_transactions_with_rewards/2" do test "without transactions" do address = insert(:address) diff --git a/apps/explorer/test/support/data_case.ex b/apps/explorer/test/support/data_case.ex index 75d3429e70..68bb21434c 100644 --- a/apps/explorer/test/support/data_case.ex +++ b/apps/explorer/test/support/data_case.ex @@ -39,7 +39,7 @@ defmodule Explorer.DataCase do Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, {:shared, self()}) end - Explorer.Chain.BlockNumberCache.setup(cache_period: 0) + Explorer.Chain.BlockNumberCache.setup() :ok end diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex index deb0e4554f..06a702aa4b 100644 --- a/apps/indexer/lib/indexer/block/fetcher.ex +++ b/apps/indexer/lib/indexer/block/fetcher.ex @@ -11,7 +11,7 @@ defmodule Indexer.Block.Fetcher do alias EthereumJSONRPC.{Blocks, FetchedBeneficiaries} alias Explorer.Chain - alias Explorer.Chain.{Address, Block, Hash, Import, Transaction} + alias Explorer.Chain.{Address, Block, BlockNumberCache, Hash, Import, Transaction} alias Indexer.Block.Fetcher.Receipts alias Indexer.Fetcher.{ @@ -171,13 +171,23 @@ defmodule Indexer.Block.Fetcher do transactions: %{params: transactions_with_receipts} } ) do - {:ok, %{inserted: inserted, errors: blocks_errors}} + result = {:ok, %{inserted: inserted, errors: blocks_errors}} + update_block_cache(inserted[:blocks]) + result else {step, {:error, reason}} -> {:error, {step, reason}} {:import, {:error, step, failed_value, changes_so_far}} -> {:error, {step, failed_value, changes_so_far}} end end + defp update_block_cache(blocks) do + max_block = Enum.max_by(blocks, fn block -> block.number end) + min_block = Enum.min_by(blocks, fn block -> block.number end) + + BlockNumberCache.update(max_block.number) + BlockNumberCache.update(min_block.number) + end + def import( %__MODULE__{broadcast: broadcast, callback_module: callback_module} = state, options