Merge pull request #8855 from blockscout/vb-address-tx-count

All transactions count at top addresses page
pull/8860/head
Victor Baranov 1 year ago committed by GitHub
commit 73ca07f740
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 4
      apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex
  3. 2
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex
  4. 2
      apps/block_scout_web/lib/block_scout_web/templates/address/_tile.html.eex
  5. 2
      apps/block_scout_web/lib/block_scout_web/templates/robots/sitemap.xml.eex
  6. 8
      apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex
  7. 6
      apps/block_scout_web/priv/gettext/default.pot
  8. 6
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  9. 4
      apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs
  10. 69
      apps/explorer/lib/explorer/chain.ex
  11. 68
      apps/explorer/lib/explorer/chain/address.ex
  12. 70
      apps/explorer/test/explorer/chain/address_test.exs
  13. 70
      apps/explorer/test/explorer/chain_test.exs

@ -15,6 +15,7 @@
### Fixes
- [#8855](https://github.com/blockscout/blockscout/pull/8855) - All transactions count at top addresses page
- [#8836](https://github.com/blockscout/blockscout/pull/8836) - Safe token update
- [#8814](https://github.com/blockscout/blockscout/pull/8814) - Improve performance for EOA addresses in `/api/v2/addresses/{address_hash}`
- [#8813](https://github.com/blockscout/blockscout/pull/8813) - Force verify twin contracts on `/api/v2/import/smart-contracts/{address_hash}`

@ -16,7 +16,7 @@ defmodule BlockScoutWeb.AddressController do
alias Explorer.{Chain, Market}
alias Explorer.Chain.Address.Counters
alias Explorer.Chain.Wei
alias Explorer.Chain.{Address, Wei}
alias Indexer.Fetcher.CoinBalanceOnDemand
alias Phoenix.View
@ -24,7 +24,7 @@ defmodule BlockScoutWeb.AddressController do
addresses =
params
|> paging_options()
|> Chain.list_top_addresses()
|> Address.list_top_addresses()
{addresses_page, next_page} = split_list_by_page(addresses)

@ -383,7 +383,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do
params
|> paging_options()
|> Keyword.merge(@api_true)
|> Chain.list_top_addresses()
|> Address.list_top_addresses()
|> split_list_by_page()
next_page_params = next_page_params(next_page, addresses, params)

@ -28,7 +28,7 @@
<span class="mr-4">
<span data-test="transaction_count">
<%= @tx_count %>
</span> <%= gettext "Transactions sent" %>
</span> <%= gettext "Transactions" %>
</span>
</td>
</tr>

@ -11,7 +11,7 @@
</url>
<% end %>
<% addresses = Chain.list_top_addresses(params) %>
<% addresses = Address.list_top_addresses(params) %>
<%= for {address, _} <- addresses do %>
<url>
<loc><%= host %>/address/<%= to_string(address) %></loc>

@ -67,10 +67,14 @@ defmodule BlockScoutWeb.API.V2.AddressView do
%{"items" => Enum.map(nft_collections, &prepare_nft_collection(&1)), "next_page_params" => next_page_params}
end
def prepare_address({address, nonce}) do
@spec prepare_address(
{atom() | %{:fetched_coin_balance => any(), :hash => any(), optional(any()) => any()}, any()}
| Explorer.Chain.Address.t()
) :: %{optional(:coin_balance) => any(), optional(:tx_count) => binary(), optional(<<_::32, _::_*8>>) => any()}
def prepare_address({address, tx_count}) do
nil
|> Helper.address_with_info(address, address.hash, true)
|> Map.put(:tx_count, to_string(nonce))
|> Map.put(:tx_count, to_string(tx_count))
|> Map.put(:coin_balance, if(address.fetched_coin_balance, do: address.fetched_coin_balance.value))
end

@ -3091,6 +3091,7 @@ msgid "Transaction type, introduced in EIP-2718."
msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:7
#: lib/block_scout_web/templates/address/_tile.html.eex:31
#: lib/block_scout_web/templates/address/overview.html.eex:186
#: lib/block_scout_web/templates/address/overview.html.eex:192
#: lib/block_scout_web/templates/address/overview.html.eex:200
@ -3109,11 +3110,6 @@ msgstr ""
msgid "Transactions and address of creation."
msgstr ""
#: lib/block_scout_web/templates/address/_tile.html.eex:31
#, elixir-autogen, elixir-format
msgid "Transactions sent"
msgstr ""
#: lib/block_scout_web/templates/address/overview.html.eex:213
#: lib/block_scout_web/templates/address/overview.html.eex:219
#: lib/block_scout_web/templates/address/overview.html.eex:227

@ -3091,6 +3091,7 @@ msgid "Transaction type, introduced in EIP-2718."
msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:7
#: lib/block_scout_web/templates/address/_tile.html.eex:31
#: lib/block_scout_web/templates/address/overview.html.eex:186
#: lib/block_scout_web/templates/address/overview.html.eex:192
#: lib/block_scout_web/templates/address/overview.html.eex:200
@ -3109,11 +3110,6 @@ msgstr ""
msgid "Transactions and address of creation."
msgstr ""
#: lib/block_scout_web/templates/address/_tile.html.eex:31
#, elixir-autogen, elixir-format
msgid "Transactions sent"
msgstr ""
#: lib/block_scout_web/templates/address/overview.html.eex:213
#: lib/block_scout_web/templates/address/overview.html.eex:219
#: lib/block_scout_web/templates/address/overview.html.eex:227

@ -1708,7 +1708,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
end
test "check nil", %{conn: conn} do
address = insert(:address, nonce: 1, fetched_coin_balance: 1)
address = insert(:address, transactions_count: 2, fetched_coin_balance: 1)
request = get(conn, "/api/v2/addresses")
@ -2477,7 +2477,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
defp compare_item(%Address{} = address, json) do
assert Address.checksum(address.hash) == json["hash"]
assert to_string(address.nonce + 1) == json["tx_count"]
assert to_string(address.transactions_count) == json["tx_count"]
end
defp compare_item(%Transaction{} = transaction, json) do

@ -68,7 +68,6 @@ defmodule Explorer.Chain do
alias Explorer.Chain.Block.{EmissionReward, Reward}
alias Explorer.Chain.Cache.{
Accounts,
BlockNumber,
Blocks,
ContractsCounter,
@ -1986,64 +1985,6 @@ defmodule Explorer.Chain do
|> Enum.into(%{})
end
@doc """
Lists the top `t:Explorer.Chain.Address.t/0`'s' in descending order based on coin balance and address hash.
"""
@spec list_top_addresses :: [{Address.t(), non_neg_integer()}]
def list_top_addresses(options \\ []) do
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
if is_nil(paging_options.key) do
paging_options.page_size
|> Accounts.take_enough()
|> case do
nil ->
get_addresses(options)
accounts ->
Enum.map(
accounts,
&{&1,
if is_nil(&1.nonce) do
0
else
&1.nonce + 1
end}
)
end
else
fetch_top_addresses(options)
end
end
defp get_addresses(options) do
accounts_with_n = fetch_top_addresses(options)
accounts_with_n
|> Enum.map(fn {address, _n} -> address end)
|> Accounts.update()
accounts_with_n
end
defp fetch_top_addresses(options) do
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
base_query =
from(a in Address,
where: a.fetched_coin_balance > ^0,
order_by: [desc: a.fetched_coin_balance, asc: a.hash],
preload: [:names, :smart_contract],
select: {a, fragment("coalesce(1 + ?, 0)", a.nonce)}
)
base_query
|> page_addresses(paging_options)
|> limit(^paging_options.page_size)
|> select_repo(options).all()
end
@doc """
Lists the top `t:Explorer.Chain.Token.t/0`'s'.
@ -3689,16 +3630,6 @@ defmodule Explorer.Chain do
end)
end
defp page_addresses(query, %PagingOptions{key: nil}), do: query
defp page_addresses(query, %PagingOptions{key: {coin_balance, hash}}) do
from(address in query,
where:
(address.fetched_coin_balance == ^coin_balance and address.hash > ^hash) or
address.fetched_coin_balance < ^coin_balance
)
end
defp page_blocks(query, %PagingOptions{key: nil}), do: query
defp page_blocks(query, %PagingOptions{key: {block_number}}) do

@ -8,7 +8,7 @@ defmodule Explorer.Chain.Address do
use Explorer.Schema
alias Ecto.Changeset
alias Explorer.Chain
alias Explorer.{Chain, PagingOptions}
alias Explorer.Chain.{
Address,
@ -25,7 +25,7 @@ defmodule Explorer.Chain.Address do
Withdrawal
}
alias Explorer.Chain.Cache.NetVersion
alias Explorer.Chain.Cache.{Accounts, NetVersion}
@optional_attrs ~w(contract_code fetched_coin_balance fetched_coin_balance_block_number nonce decompiled verified gas_used transactions_count token_transfers_count)a
@required_attrs ~w(hash)a
@ -304,4 +304,68 @@ defmodule Explorer.Chain.Address do
@for.checksum(address)
end
end
@default_paging_options %PagingOptions{page_size: 50}
@doc """
Lists the top `t:Explorer.Chain.Address.t/0`'s' in descending order based on coin balance and address hash.
"""
@spec list_top_addresses :: [{Address.t(), non_neg_integer()}]
def list_top_addresses(options \\ []) do
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
if is_nil(paging_options.key) do
paging_options.page_size
|> Accounts.take_enough()
|> case do
nil ->
get_addresses(options)
accounts ->
Enum.map(
accounts,
&{&1, &1.transactions_count || 0}
)
end
else
fetch_top_addresses(options)
end
end
defp get_addresses(options) do
accounts_with_n = fetch_top_addresses(options)
accounts_with_n
|> Enum.map(fn {address, _n} -> address end)
|> Accounts.update()
accounts_with_n
end
defp fetch_top_addresses(options) do
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
base_query =
from(a in Address,
where: a.fetched_coin_balance > ^0,
order_by: [desc: a.fetched_coin_balance, asc: a.hash],
preload: [:names, :smart_contract],
select: {a, a.transactions_count}
)
base_query
|> page_addresses(paging_options)
|> limit(^paging_options.page_size)
|> Chain.select_repo(options).all()
end
defp page_addresses(query, %PagingOptions{key: nil}), do: query
defp page_addresses(query, %PagingOptions{key: {coin_balance, hash}}) do
from(address in query,
where:
(address.fetched_coin_balance == ^coin_balance and address.hash > ^hash) or
address.fetched_coin_balance < ^coin_balance
)
end
end

@ -63,4 +63,74 @@ defmodule Explorer.Chain.AddressTest do
assert str("0xd1220a0cf47c7b9be7a2e6ba89f429762e7b9adb") == "0xD1220A0Cf47c7B9BE7a2e6ba89F429762E7B9adB"
end
end
describe "list_top_addresses/0" do
test "without addresses with balance > 0" do
insert(:address, fetched_coin_balance: 0)
assert [] = Address.list_top_addresses()
end
test "with top addresses in order" do
address_hashes =
4..1
|> Enum.map(&insert(:address, fetched_coin_balance: &1))
|> Enum.map(& &1.hash)
assert address_hashes ==
Address.list_top_addresses()
|> Enum.map(fn {address, _transaction_count} -> address end)
|> Enum.map(& &1.hash)
end
# flaky test
# test "with top addresses in order with matching value" do
# test_hashes =
# 4..0
# |> Enum.map(&Explorer.Chain.Hash.cast(Explorer.Chain.Hash.Address, &1))
# |> Enum.map(&elem(&1, 1))
# tail =
# 4..1
# |> Enum.map(&insert(:address, fetched_coin_balance: &1, hash: Enum.fetch!(test_hashes, &1 - 1)))
# |> Enum.map(& &1.hash)
# first_result_hash =
# :address
# |> insert(fetched_coin_balance: 4, hash: Enum.fetch!(test_hashes, 4))
# |> Map.fetch!(:hash)
# assert [first_result_hash | tail] ==
# Address.list_top_addresses()
# |> Enum.map(fn {address, _transaction_count} -> address end)
# |> Enum.map(& &1.hash)
# end
# flaky test
# test "paginates addresses" do
# test_hashes =
# 4..0
# |> Enum.map(&Explorer.Chain.Hash.cast(Explorer.Chain.Hash.Address, &1))
# |> Enum.map(&elem(&1, 1))
# result =
# 4..1
# |> Enum.map(&insert(:address, fetched_coin_balance: &1, hash: Enum.fetch!(test_hashes, &1 - 1)))
# |> Enum.map(& &1.hash)
# options = [paging_options: %PagingOptions{page_size: 1}]
# [{top_address, _}] = Chain.list_top_addresses(options)
# assert top_address.hash == List.first(result)
# tail_options = [
# paging_options: %PagingOptions{key: {top_address.fetched_coin_balance.value, top_address.hash}, page_size: 3}
# ]
# tail_result = tail_options |> Address.list_top_addresses() |> Enum.map(fn {address, _} -> address.hash end)
# [_ | expected_tail] = result
# assert tail_result == expected_tail
# end
end
end

@ -2058,76 +2058,6 @@ defmodule Explorer.ChainTest do
end
end
describe "list_top_addresses/0" do
test "without addresses with balance > 0" do
insert(:address, fetched_coin_balance: 0)
assert [] = Chain.list_top_addresses()
end
test "with top addresses in order" do
address_hashes =
4..1
|> Enum.map(&insert(:address, fetched_coin_balance: &1))
|> Enum.map(& &1.hash)
assert address_hashes ==
Chain.list_top_addresses()
|> Enum.map(fn {address, _transaction_count} -> address end)
|> Enum.map(& &1.hash)
end
# flaky test
# test "with top addresses in order with matching value" do
# test_hashes =
# 4..0
# |> Enum.map(&Explorer.Chain.Hash.cast(Explorer.Chain.Hash.Address, &1))
# |> Enum.map(&elem(&1, 1))
# tail =
# 4..1
# |> Enum.map(&insert(:address, fetched_coin_balance: &1, hash: Enum.fetch!(test_hashes, &1 - 1)))
# |> Enum.map(& &1.hash)
# first_result_hash =
# :address
# |> insert(fetched_coin_balance: 4, hash: Enum.fetch!(test_hashes, 4))
# |> Map.fetch!(:hash)
# assert [first_result_hash | tail] ==
# Chain.list_top_addresses()
# |> Enum.map(fn {address, _transaction_count} -> address end)
# |> Enum.map(& &1.hash)
# end
# flaky test
# test "paginates addresses" do
# test_hashes =
# 4..0
# |> Enum.map(&Explorer.Chain.Hash.cast(Explorer.Chain.Hash.Address, &1))
# |> Enum.map(&elem(&1, 1))
# result =
# 4..1
# |> Enum.map(&insert(:address, fetched_coin_balance: &1, hash: Enum.fetch!(test_hashes, &1 - 1)))
# |> Enum.map(& &1.hash)
# options = [paging_options: %PagingOptions{page_size: 1}]
# [{top_address, _}] = Chain.list_top_addresses(options)
# assert top_address.hash == List.first(result)
# tail_options = [
# paging_options: %PagingOptions{key: {top_address.fetched_coin_balance.value, top_address.hash}, page_size: 3}
# ]
# tail_result = tail_options |> Chain.list_top_addresses() |> Enum.map(fn {address, _} -> address.hash end)
# [_ | expected_tail] = result
# assert tail_result == expected_tail
# end
end
describe "stream_blocks_without_rewards/2" do
test "includes consensus blocks" do
%Block{hash: consensus_hash} = insert(:block, consensus: true)

Loading…
Cancel
Save