Merge pull request #1714 from poanetwork/ab-fix-average-block-time

fix average block time calculation
pull/1724/head
Ayrat Badykov 6 years ago committed by GitHub
commit f5216038aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 2
      apps/block_scout_web/lib/block_scout_web/notifier.ex
  3. 59
      apps/explorer/lib/explorer/counters/average_block_time.ex
  4. 38
      apps/explorer/test/explorer/counters/average_block_time_test.exs
  5. 14
      apps/indexer/test/indexer/coin_balance/on_demand_fetcher_test.exs

@ -18,6 +18,7 @@
- [#1699](https://github.com/poanetwork/blockscout/pull/1699) - use seconds as transaction cache period measure
- [#1697](https://github.com/poanetwork/blockscout/pull/1697) - fix failing in rpc if balance is empty
- [#1711](https://github.com/poanetwork/blockscout/pull/1711) - rescue failing repo in block number cache update
- [#1714](https://github.com/poanetwork/blockscout/pull/1714) - fix average block time calculation
### Chore

@ -116,7 +116,7 @@ defmodule BlockScoutWeb.Notifier do
defp broadcast_block(block) do
preloaded_block = Repo.preload(block, [[miner: :names], :transactions, :rewards])
average_block_time = AverageBlockTime.average_block_time(preloaded_block)
average_block_time = AverageBlockTime.average_block_time()
Endpoint.broadcast("blocks:new_block", "new_block", %{
block: preloaded_block,

@ -11,6 +11,8 @@ defmodule Explorer.Counters.AverageBlockTime do
alias Explorer.Repo
alias Timex.Duration
@refresh_period 30 * 60 * 1_000
@doc """
Starts a process to periodically update the counter of the token holders.
"""
@ -19,27 +21,51 @@ defmodule Explorer.Counters.AverageBlockTime do
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
end
def average_block_time(block \\ nil) do
def average_block_time do
enabled? =
:explorer
|> Application.fetch_env!(__MODULE__)
|> Keyword.fetch!(:enabled)
if enabled? do
block = if block, do: {block.number, DateTime.to_unix(block.timestamp, :millisecond)}
GenServer.call(__MODULE__, {:average_block_time, block})
GenServer.call(__MODULE__, :average_block_time)
else
{:error, :disabled}
end
end
def refresh do
GenServer.call(__MODULE__, :refresh_timestamps)
end
## Server
@impl true
def init(_) do
Process.send_after(self(), :refresh_timestamps, @refresh_period)
{:ok, refresh_timestamps()}
end
@impl true
def handle_call(:average_block_time, _from, %{average: average} = state), do: {:reply, average, state}
@impl true
def handle_call(:refresh_timestamps, _, _) do
{:reply, :ok, refresh_timestamps()}
end
@impl true
def handle_info(:refresh_timestamps, _) do
Process.send_after(self(), :refresh_timestamps, @refresh_period)
{:noreply, refresh_timestamps()}
end
defp refresh_timestamps do
timestamps_query =
from(block in Block,
limit: 100,
offset: 1,
offset: 0,
order_by: [desc: block.number],
select: {block.number, block.timestamp}
)
@ -51,30 +77,7 @@ defmodule Explorer.Counters.AverageBlockTime do
{number, DateTime.to_unix(timestamp, :millisecond)}
end)
{:ok, %{timestamps: timestamps, average: average_distance(timestamps)}}
end
@impl true
def handle_call({:average_block_time, nil}, _from, %{average: average} = state), do: {:reply, average, state}
def handle_call({:average_block_time, block}, _from, state) do
state = add_block(state, block)
{:reply, state.average, state}
end
# This is pretty naive, but we'll only ever be sorting 100 dates so I don't think
# complex logic is really necessary here.
defp add_block(%{timestamps: timestamps} = state, {new_number, _} = block) do
if Enum.any?(timestamps, fn {number, _} -> number == new_number end) do
state
else
timestamps =
[block | timestamps]
|> Enum.sort_by(fn {number, _} -> number end, &Kernel.>/2)
|> Enum.take(100)
%{state | timestamps: timestamps, average: average_distance(timestamps)}
end
%{timestamps: timestamps, average: average_distance(timestamps)}
end
defp average_distance([]), do: Duration.from_milliseconds(0)

@ -5,8 +5,6 @@ defmodule Explorer.Counters.AverageBlockTimeTest do
alias Explorer.Counters.AverageBlockTime
defp block(number, last, duration), do: %{number: number, timestamp: Timex.shift(last, seconds: duration)}
setup do
start_supervised!(AverageBlockTime)
Application.put_env(:explorer, AverageBlockTime, enabled: true)
@ -26,41 +24,5 @@ defmodule Explorer.Counters.AverageBlockTimeTest do
test "without blocks duration is 0" do
assert AverageBlockTime.average_block_time() == Timex.Duration.parse!("PT0S")
end
test "with only one block, the duration is 0" do
now = Timex.now()
block = block(0, now, 0)
assert AverageBlockTime.average_block_time(block) == Timex.Duration.parse!("PT0S")
end
test "once there are two blocks, the duration is the average distance between them all" do
now = Timex.now()
block0 = block(0, now, 0)
block1 = block(1, now, 2)
block2 = block(2, now, 6)
AverageBlockTime.average_block_time(block0)
assert AverageBlockTime.average_block_time(block1) == Timex.Duration.parse!("PT2S")
assert AverageBlockTime.average_block_time(block2) == Timex.Duration.parse!("PT3S")
end
test "only the last 100 blocks are considered" do
now = Timex.now()
block0 = block(0, now, 0)
block1 = block(1, now, 2000)
AverageBlockTime.average_block_time(block0)
AverageBlockTime.average_block_time(block1)
for i <- 1..100 do
block = block(i + 1, now, 2000 + i)
AverageBlockTime.average_block_time(block)
end
assert AverageBlockTime.average_block_time() == Timex.Duration.parse!("PT1S")
end
end
end

@ -41,10 +41,9 @@ defmodule Indexer.CoinBalance.OnDemandFetcherTest do
# we space these very far apart so that we know it will consider the 0th block stale (it calculates how far
# back we'd need to go to get 24 hours in the past)
block_0 = insert(:block, number: 0, timestamp: Timex.shift(now, hours: -50))
AverageBlockTime.average_block_time(block_0)
block_1 = insert(:block, number: 1, timestamp: now)
AverageBlockTime.average_block_time(block_1)
insert(:block, number: 0, timestamp: Timex.shift(now, hours: -50))
insert(:block, number: 1, timestamp: now)
AverageBlockTime.refresh()
stale_address = insert(:address, fetched_coin_balance: 1, fetched_coin_balance_block_number: 0)
current_address = insert(:address, fetched_coin_balance: 1, fetched_coin_balance_block_number: 1)
@ -89,10 +88,9 @@ defmodule Indexer.CoinBalance.OnDemandFetcherTest do
# we space these very far apart so that we know it will consider the 0th block stale (it calculates how far
# back we'd need to go to get 24 hours in the past)
block_0 = insert(:block, number: 0, timestamp: Timex.shift(now, hours: -50))
AverageBlockTime.average_block_time(block_0)
block_1 = insert(:block, number: 1, timestamp: now)
AverageBlockTime.average_block_time(block_1)
insert(:block, number: 0, timestamp: Timex.shift(now, hours: -50))
insert(:block, number: 1, timestamp: now)
AverageBlockTime.refresh()
:ok
end

Loading…
Cancel
Save