parent
8a2f87ae5b
commit
d0009784af
@ -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…
Reference in new issue