Merge pull request #3146 from poanetwork/vb-refine-coin-balance-history

Fix coin balance history page: order of items, fix if no balance changes
pull/3149/head
Victor Baranov 5 years ago committed by GitHub
commit 0e8c627c08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 12
      apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex
  3. 99
      apps/explorer/lib/explorer/chain.ex
  4. 48
      apps/explorer/lib/explorer/chain/address/coin_balance.ex

@ -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 - [#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 ### 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) - [#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 - [#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 - [#3133](https://github.com/poanetwork/blockscout/pull/3133) - Take into account FIRST_BLOCK in trace_ReplayBlockTransactions requests

@ -23,16 +23,6 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do
{coin_balances, next_page} = split_list_by_page(coin_balances_plus_one) {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 = next_page_url =
case next_page_params(next_page, coin_balances, params) do case next_page_params(next_page, coin_balances, params) do
nil -> nil ->
@ -48,7 +38,7 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do
end end
coin_balances_json = coin_balances_json =
Enum.map(deduplicated_coin_balances, fn coin_balance -> Enum.map(coin_balances, fn coin_balance ->
View.render_to_string( View.render_to_string(
AddressCoinBalanceView, AddressCoinBalanceView,
"_coin_balances.html", "_coin_balances.html",

@ -3499,56 +3499,63 @@ defmodule Explorer.Chain do
|> page_coin_balances(paging_options) |> page_coin_balances(paging_options)
|> Repo.all() |> Repo.all()
min_block_number = if Enum.empty?(balances_raw) do
balances_raw balances_raw
|> Enum.min_by(fn balance -> balance.block_number end) else
|> Map.get(:block_number) balances_raw_filtered =
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
balances_raw balances_raw
|> Enum.map(fn balance -> |> Enum.filter(fn balance -> balance.value end)
date = min_block_unix_timestamp
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) formatted_date = Timex.from_unix(date)
%{balance | block_timestamp: formatted_date} %{balance | block_timestamp: formatted_date}
end) end)
end end
balances_with_dates balances_with_dates
|> Enum.filter(fn balance -> balance.value end) |> Enum.sort(fn balance1, balance2 -> balance1.block_number >= balance2.block_number end)
|> Enum.sort(fn balance1, balance2 -> balance1.block_timestamp >= balance2.block_timestamp end) end
end end
def get_coin_balance(address_hash, block_number) do def get_coin_balance(address_hash, block_number) do

@ -72,13 +72,18 @@ defmodule Explorer.Chain.Address.CoinBalance do
The last coin balance from an Address is the last block indexed. The last coin balance from an Address is the last block indexed.
""" """
def fetch_coin_balances(address_hash, %PagingOptions{page_size: page_size}) do def fetch_coin_balances(address_hash, %PagingOptions{page_size: page_size}) do
from( query =
cb in CoinBalance, from(
where: cb.address_hash == ^address_hash, cb in CoinBalance,
where: not is_nil(cb.value), where: cb.address_hash == ^address_hash,
order_by: [desc: :block_number], where: not is_nil(cb.value),
limit: ^page_size, order_by: [desc: :block_number],
select_merge: %{delta: fragment("value - coalesce(lead(value, 1) over (order by block_number desc), 0)")} 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 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. 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 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 CoinBalance
|> join(:inner, [cb], b in Block, on: cb.block_number == b.number) |> join(:inner, [cb], b in Block, on: cb.block_number == b.number)
|> where([cb], cb.address_hash == ^address_hash) |> 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)) |> group_by([cb, b], fragment("date_trunc('day', ?)", b.timestamp))
|> order_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)}) |> select([cb, b], %{date: type(fragment("date_trunc('day', ?)", b.timestamp), :date), value: max(cb.value)})
end end
def limit_time_interval(query, nil) do def limit_time_interval(query, days_to_consider, nil) do
query |> where([cb, b], b.timestamp >= fragment("date_trunc('day', now()) - interval '90 days'")) query
|> where(
[cb, b],
b.timestamp >=
fragment("date_trunc('day', now() - CAST(? AS INTERVAL))", ^%Postgrex.Interval{days: days_to_consider})
)
end end
def limit_time_interval(query, %{timestamp: timestamp}) do def limit_time_interval(query, days_to_consider, %{timestamp: timestamp}) do
query |> where([cb, b], b.timestamp >= fragment("(? AT TIME ZONE ?) - interval '90 days'", ^timestamp, ^"Etc/UTC")) query
|> where(
[cb, b],
b.timestamp >=
fragment(
"(? AT TIME ZONE ?) - CAST(? AS INTERVAL)",
^timestamp,
^"Etc/UTC",
^%Postgrex.Interval{days: days_to_consider}
)
)
end end
def last_coin_balance_timestamp(address_hash) do def last_coin_balance_timestamp(address_hash) do

Loading…
Cancel
Save