diff --git a/CHANGELOG.md b/CHANGELOG.md index c222d86c10..65627e9e12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - [#3125](https://github.com/poanetwork/blockscout/pull/3125) - Availability to configure a number of days to consider at coin balance history chart via environment variable ### Fixes +- [#3146](https://github.com/poanetwork/blockscout/pull/3146) - Fix coin balance history page: order of items, fix if no balance changes - [#3142](https://github.com/poanetwork/blockscout/pull/3142) - Speed-up last coin balance timestamp query (coin balance history page performance improvement) - [#3140](https://github.com/poanetwork/blockscout/pull/3140) - Fix performance of the balance changing history list loading - [#3133](https://github.com/poanetwork/blockscout/pull/3133) - Take into account FIRST_BLOCK in trace_ReplayBlockTransactions requests diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex index b4e21e3393..538f8fd7f4 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex @@ -23,16 +23,6 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do {coin_balances, next_page} = split_list_by_page(coin_balances_plus_one) - deduplicated_coin_balances = - coin_balances - |> Enum.dedup_by(fn record -> - if record.delta == Decimal.new(0) do - :dup - else - System.unique_integer() - end - end) - next_page_url = case next_page_params(next_page, coin_balances, params) do nil -> @@ -48,7 +38,7 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do end coin_balances_json = - Enum.map(deduplicated_coin_balances, fn coin_balance -> + Enum.map(coin_balances, fn coin_balance -> View.render_to_string( AddressCoinBalanceView, "_coin_balances.html", diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index f89e94a365..9fc94e6af0 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -3499,56 +3499,63 @@ defmodule Explorer.Chain do |> page_coin_balances(paging_options) |> Repo.all() - min_block_number = + if Enum.empty?(balances_raw) do balances_raw - |> Enum.min_by(fn balance -> balance.block_number end) - |> Map.get(:block_number) - - max_block_number = - balances_raw - |> Enum.max_by(fn balance -> balance.block_number end) - |> Map.get(:block_number) - - min_block_timestamp = find_block_timestamp(min_block_number) - max_block_timestamp = find_block_timestamp(max_block_number) - - min_block_unix_timestamp = - min_block_timestamp - |> Timex.to_unix() - - max_block_unix_timestamp = - max_block_timestamp - |> Timex.to_unix() - - blocks_delta = max_block_number - min_block_number - - balances_with_dates = - if blocks_delta > 0 do - balances_raw - |> Enum.map(fn balance -> - date = - trunc( - min_block_unix_timestamp + - (balance.block_number - min_block_number) * (max_block_unix_timestamp - min_block_unix_timestamp) / - blocks_delta - ) - - formatted_date = Timex.from_unix(date) - %{balance | block_timestamp: formatted_date} - end) - else + else + balances_raw_filtered = balances_raw - |> Enum.map(fn balance -> - date = min_block_unix_timestamp + |> Enum.filter(fn balance -> balance.value end) + + min_block_number = + balances_raw_filtered + |> Enum.min_by(fn balance -> balance.block_number end, fn -> %{} end) + |> Map.get(:block_number) + + max_block_number = + balances_raw_filtered + |> Enum.max_by(fn balance -> balance.block_number end, fn -> %{} end) + |> Map.get(:block_number) + + min_block_timestamp = find_block_timestamp(min_block_number) + max_block_timestamp = find_block_timestamp(max_block_number) + + min_block_unix_timestamp = + min_block_timestamp + |> Timex.to_unix() + + max_block_unix_timestamp = + max_block_timestamp + |> Timex.to_unix() + + blocks_delta = max_block_number - min_block_number + + balances_with_dates = + if blocks_delta > 0 do + balances_raw_filtered + |> Enum.map(fn balance -> + date = + trunc( + min_block_unix_timestamp + + (balance.block_number - min_block_number) * (max_block_unix_timestamp - min_block_unix_timestamp) / + blocks_delta + ) + + formatted_date = Timex.from_unix(date) + %{balance | block_timestamp: formatted_date} + end) + else + balances_raw_filtered + |> Enum.map(fn balance -> + date = min_block_unix_timestamp - formatted_date = Timex.from_unix(date) - %{balance | block_timestamp: formatted_date} - end) - end + formatted_date = Timex.from_unix(date) + %{balance | block_timestamp: formatted_date} + end) + end - balances_with_dates - |> Enum.filter(fn balance -> balance.value end) - |> Enum.sort(fn balance1, balance2 -> balance1.block_timestamp >= balance2.block_timestamp end) + balances_with_dates + |> Enum.sort(fn balance1, balance2 -> balance1.block_number >= balance2.block_number end) + end end def get_coin_balance(address_hash, block_number) do diff --git a/apps/explorer/lib/explorer/chain/address/coin_balance.ex b/apps/explorer/lib/explorer/chain/address/coin_balance.ex index ca693b84a8..cada366173 100644 --- a/apps/explorer/lib/explorer/chain/address/coin_balance.ex +++ b/apps/explorer/lib/explorer/chain/address/coin_balance.ex @@ -72,13 +72,18 @@ defmodule Explorer.Chain.Address.CoinBalance do The last coin balance from an Address is the last block indexed. """ def fetch_coin_balances(address_hash, %PagingOptions{page_size: page_size}) do - from( - cb in CoinBalance, - where: cb.address_hash == ^address_hash, - where: not is_nil(cb.value), - order_by: [desc: :block_number], - limit: ^page_size, - select_merge: %{delta: fragment("value - coalesce(lead(value, 1) over (order by block_number desc), 0)")} + query = + from( + cb in CoinBalance, + where: cb.address_hash == ^address_hash, + where: not is_nil(cb.value), + order_by: [desc: :block_number], + select_merge: %{delta: fragment("value - coalesce(lead(value, 1) over (order by block_number desc), 0)")} + ) + + from(balance in subquery(query), + where: balance.delta != 0, + limit: ^page_size ) end @@ -87,21 +92,40 @@ defmodule Explorer.Chain.Address.CoinBalance do corresponds to the maximum balance in that day. Only the last 90 days of data are used. """ def balances_by_day(address_hash, block_timestamp \\ nil) do + {days_to_consider, _} = + Application.get_env(:block_scout_web, BlockScoutWeb.Chain.Address.CoinBalance)[:coin_balance_history_days] + |> Integer.parse() + CoinBalance |> join(:inner, [cb], b in Block, on: cb.block_number == b.number) |> where([cb], cb.address_hash == ^address_hash) - |> limit_time_interval(block_timestamp) + |> limit_time_interval(days_to_consider, block_timestamp) |> group_by([cb, b], fragment("date_trunc('day', ?)", b.timestamp)) |> order_by([cb, b], fragment("date_trunc('day', ?)", b.timestamp)) |> select([cb, b], %{date: type(fragment("date_trunc('day', ?)", b.timestamp), :date), value: max(cb.value)}) end - def limit_time_interval(query, nil) do - query |> where([cb, b], b.timestamp >= fragment("date_trunc('day', now()) - interval '90 days'")) + def limit_time_interval(query, days_to_consider, nil) do + query + |> where( + [cb, b], + b.timestamp >= + fragment("date_trunc('day', now() - CAST(? AS INTERVAL))", ^%Postgrex.Interval{days: days_to_consider}) + ) end - def limit_time_interval(query, %{timestamp: timestamp}) do - query |> where([cb, b], b.timestamp >= fragment("(? AT TIME ZONE ?) - interval '90 days'", ^timestamp, ^"Etc/UTC")) + def limit_time_interval(query, days_to_consider, %{timestamp: timestamp}) do + query + |> where( + [cb, b], + b.timestamp >= + fragment( + "(? AT TIME ZONE ?) - CAST(? AS INTERVAL)", + ^timestamp, + ^"Etc/UTC", + ^%Postgrex.Interval{days: days_to_consider} + ) + ) end def last_coin_balance_timestamp(address_hash) do