Added Transaction.History.Historian and TransactionStats w/ migration

pull/2404/head
slightlycyborg 5 years ago
parent 8a2f87ae5b
commit d0009784af
  1. 53
      apps/explorer/lib/explorer/chain/transaction/history/historian.ex
  2. 37
      apps/explorer/lib/explorer/chain/transaction/history/transaction_stats.ex
  3. 12
      apps/explorer/priv/repo/migrations/20190709043832_create_transaction_stats.exs
  4. 94
      apps/explorer/test/explorer/chain/transaction/history/historian_test.exs
  5. 30
      apps/explorer/test/explorer/chain/transaction/history/transaction_stats_test.exs

@ -0,0 +1,53 @@
defmodule Explorer.Chain.Transaction.History.Historian do
use Explorer.History.Historian
alias Explorer.History.Process, as: HistoryProcess
alias Explorer.Repo
alias Explorer.Chain.Block
alias Explorer.Chain.Transaction.History.TransactionStats
import Ecto.Query, only: [from: 2]
alias Explorer.Chain.Transaction
@behaviour Historian
@impl Historian
def compile_records(num_days, records \\ []) do
if num_days == 0 do
#base case
{:ok, records}
else
day_to_fetch = Date.add(date_today(), -1*(num_days-1))
earliest = datetime(day_to_fetch, ~T[00:00:00])
latest = datetime(day_to_fetch, ~T[23:59:59])
query = from(
block in Block,
where: (block.timestamp >= ^earliest and block.timestamp <= ^latest),
join: transaction in Transaction,
on: block.hash == transaction.block_hash)
num_transactions = Repo.aggregate query, :count, :hash
records = [%{date: day_to_fetch, number_of_transactions: num_transactions} | records ]
compile_records(num_days-1, records)
end
end
@impl Historian
def save_records(records) do
{num_inserted, _} = Repo.insert_all(TransactionStats, records, on_conflict: :replace_all, conflict_target: [:date])
num_inserted
end
@spec datetime(Date.t(), Time.t()) :: DateTime.t()
defp datetime(date, time) do
{_success?, naive_dt} = NaiveDateTime.new(date, time)
DateTime.from_naive!(naive_dt, "Etc/UTC")
end
defp date_today() do
HistoryProcess.config_or_default(:utc_today, Date.utc_today(), __MODULE__)
end
end

@ -0,0 +1,37 @@
defmodule Explorer.Chain.Transaction.History.TransactionStats do
@moduledoc """
Represents daily transaction numbers.
"""
import Ecto.Query, only: [from: 2]
use Explorer.Schema
alias Explorer.Repo
schema "transaction_stats" do
field(:date, :date)
field(:number_of_transactions, :integer)
end
@typedoc """
The recorded values of the number of transactions for a single day.
* `:date` - The date in UTC.
* `:number_of_transactions` - Number of transactions processed by the vm for a given date.
"""
@type t :: %__MODULE__{
date: Date.t(),
number_of_transactions: Integer.t()
}
@spec by_date_range(Date.t(), Date.t()) :: [__MODULE__]
def by_date_range(earliest, latest) do
# Create a query
query = from stat in __MODULE__,
where: (stat.date >= ^earliest and stat.date<=^latest),
order_by: [desc: :date]
Repo.all(query)
end
end

@ -0,0 +1,12 @@
defmodule Explorer.Repo.Migrations.CreateTransactionStats do
use Ecto.Migration
def change do
create table(:transaction_stats) do
add(:date, :date)
add(:number_of_transactions, :integer)
end
create(unique_index(:transaction_stats, :date))
end
end

@ -0,0 +1,94 @@
defmodule Explorer.Chain.Transaction.History.HistorianTest do
use Explorer.DataCase, async: false
alias Explorer.Chain.Transaction.History.Historian
alias Explorer.Chain.Transaction.History.TransactionStats
import Ecto.Query, only: [from: 2]
setup do
Application.put_env(:explorer, Historian, utc_today: ~D[1970-01-04])
:ok
end
defp days_to_secs(days) do
60*60*24*days
end
describe "compile_records/1" do
test "fetches transactions from blocks mined in the past num_days" do
blocks = [
#1970-01-03 00:00:60
insert(:block, timestamp: DateTime.from_unix!(days_to_secs(2) + 60)),
#1970-01-03 04:00:00
insert(:block, timestamp: DateTime.from_unix!(days_to_secs(2) + (4*60*60))),
#1970-01-02 00:00:00
insert(:block, timestamp: DateTime.from_unix!(days_to_secs(1)))
]
insert(:transaction) |> with_block(Enum.at(blocks, 0))
insert(:transaction) |> with_block(Enum.at(blocks, 1))
insert(:transaction) |> with_block(Enum.at(blocks, 2))
expected = [
%{date: ~D[1970-01-04], number_of_transactions: 0}
]
assert {:ok, ^expected} = Historian.compile_records 1
expected = [
%{date: ~D[1970-01-04], number_of_transactions: 0},
%{date: ~D[1970-01-03], number_of_transactions: 2},
]
assert {:ok, ^expected} = Historian.compile_records 2
expected = [
%{date: ~D[1970-01-04], number_of_transactions: 0},
%{date: ~D[1970-01-03], number_of_transactions: 2},
%{date: ~D[1970-01-02], number_of_transactions: 1}
]
assert {:ok, ^expected} = Historian.compile_records 3
end
end
describe "save_records/1" do
test "saves transaction history records" do
records = [
%{date: ~D[1970-01-04], number_of_transactions: 3},
%{date: ~D[1970-01-03], number_of_transactions: 2},
%{date: ~D[1970-01-02], number_of_transactions: 1}
]
Historian.save_records(records)
query = from(
stats in TransactionStats,
select: %{date: stats.date, number_of_transactions: stats.number_of_transactions},
order_by: [desc: stats.date])
results = Repo.all(query)
assert 3 == length(results)
assert ^records = results
end
test "overwrites records with the same date without error" do
records = [%{date: ~D[1970-01-04], number_of_transactions: 3}]
Historian.save_records(records)
records = [%{date: ~D[1970-01-04], number_of_transactions: 1}]
Historian.save_records(records)
end
end
@tag capture_log: true
test "start_link" do
assert {:ok, _} = Historian.start_link([])
end
end

@ -0,0 +1,30 @@
defmodule Explorer.Chain.Transaction.History.TransactionStatsTest do
use Explorer.DataCase, async: false
alias Explorer.Chain.Transaction.History.TransactionStats
alias Explorer.Repo
test "by_date_range()" do
some_transaction_stats = [%{date: ~D[2019-07-09], number_of_transactions: 10},
%{date: ~D[2019-07-08], number_of_transactions: 20},
%{date: ~D[2019-07-07], number_of_transactions: 30}]
Repo.insert_all(TransactionStats, some_transaction_stats)
all3 = TransactionStats.by_date_range(~D[2019-07-07], ~D[2019-07-09])
assert 3 = length(all3)
assert ~D[2019-07-09] = Enum.at(all3, 0).date
assert 10 == Enum.at(all3, 0).number_of_transactions
assert ~D[2019-07-08] = Enum.at(all3, 1).date
assert 20 == Enum.at(all3, 1).number_of_transactions
assert ~D[2019-07-07] = Enum.at(all3, 2).date
assert 30 == Enum.at(all3, 2).number_of_transactions
just2 = TransactionStats.by_date_range(~D[2019-07-08], ~D[2019-07-09])
assert 2 == length(just2)
assert ~D[2019-07-08] = Enum.at(just2, 1).date
end
end
Loading…
Cancel
Save