Add a counter to store the token tranfers count info

Co-authored-by: Lucas Narciso <lucas.narciso@plataformatec.com.br>
pull/867/head
Amanda Sposito 6 years ago
parent bc64611bc9
commit 2d02ed6e24
  1. 2
      apps/explorer/config/config.exs
  2. 6
      apps/explorer/lib/explorer/application.ex
  3. 16
      apps/explorer/lib/explorer/chain/token_transfer.ex
  4. 93
      apps/explorer/lib/explorer/counters/token_transfer_counter.ex
  5. 12
      apps/explorer/test/explorer/chain/token_transfer_test.exs
  6. 50
      apps/explorer/test/explorer/counters/token_transfer_counter_test.exs

@ -22,6 +22,8 @@ config :explorer, Explorer.Repo,
loggers: [Explorer.Repo.PrometheusLogger, Ecto.LogEntry], loggers: [Explorer.Repo.PrometheusLogger, Ecto.LogEntry],
migration_timestamps: [type: :utc_datetime] migration_timestamps: [type: :utc_datetime]
config :explorer, Explorer.Counters.TokenTransferCounter, enabled: true
config :explorer, config :explorer,
solc_bin_api_url: "https://solc-bin.ethereum.org" solc_bin_api_url: "https://solc-bin.ethereum.org"

@ -16,6 +16,9 @@ defmodule Explorer.Application do
Explorer.Repo, Explorer.Repo,
Supervisor.child_spec({Task.Supervisor, name: Explorer.MarketTaskSupervisor}, id: Explorer.MarketTaskSupervisor), Supervisor.child_spec({Task.Supervisor, name: Explorer.MarketTaskSupervisor}, id: Explorer.MarketTaskSupervisor),
Supervisor.child_spec({Task.Supervisor, name: Explorer.TaskSupervisor}, id: Explorer.TaskSupervisor), Supervisor.child_spec({Task.Supervisor, name: Explorer.TaskSupervisor}, id: Explorer.TaskSupervisor),
Supervisor.child_spec({Task.Supervisor, name: Explorer.CounterTokenSupervisor},
id: Explorer.CounterTokenSupervisor
),
{Registry, keys: :duplicate, name: Registry.ChainEvents, id: Registry.ChainEvents} {Registry, keys: :duplicate, name: Registry.ChainEvents, id: Registry.ChainEvents}
] ]
@ -29,7 +32,8 @@ defmodule Explorer.Application do
defp configurable_children do defp configurable_children do
[ [
configure(Explorer.ExchangeRates), configure(Explorer.ExchangeRates),
configure(Explorer.Market.History.Cataloger) configure(Explorer.Market.History.Cataloger),
configure(Explorer.Counters.TokenTransferCounter)
] ]
|> List.flatten() |> List.flatten()
end end

@ -197,4 +197,20 @@ defmodule Explorer.Chain.TokenTransfer do
select: tt select: tt
) )
end end
@doc """
Counts all the token transfers and groups by token contract address hash.
"""
def count_token_transfers do
query =
from(
tt in TokenTransfer,
join: t in Token,
on: tt.token_contract_address_hash == t.contract_address_hash,
select: {tt.token_contract_address_hash, count(tt.id)},
group_by: tt.token_contract_address_hash
)
Repo.all(query)
end
end end

@ -0,0 +1,93 @@
defmodule Explorer.Counters.TokenTransferCounter do
use GenServer
@moduledoc """
Module responsible for fetching and consolidating the number of transfers
from a token.
"""
alias Explorer.Chain
alias Explorer.Chain.{Hash, TokenTransfer}
@table :token_transfer_counter
def table_name do
@table
end
@doc """
Starts a process to continually monitor the token counters.
"""
@spec start_link(term()) :: GenServer.on_start()
def start_link(_) do
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
end
## Server
@impl true
def init(args) do
create_table()
Task.start_link(&consolidate/0)
Chain.subscribe_to_events(:token_transfers)
{:ok, args}
end
def create_table do
opts = [
:set,
:named_table,
:public,
read_concurrency: true,
write_concurrency: true
]
:ets.new(table_name(), opts)
end
@doc """
Consolidates the number of token transfers grouped by token.
"""
def consolidate do
total_token_transfers = TokenTransfer.count_token_transfers()
for {token_hash, total} <- total_token_transfers do
insert_or_update_counter(token_hash, total)
end
end
@doc """
Fetches the number of transfers related to a token hash.
"""
@spec fetch(Hash.t()) :: non_neg_integer
def fetch(token_hash) do
do_fetch(:ets.lookup(table_name(), to_string(token_hash)))
end
defp do_fetch([{_, result} | _]), do: result
defp do_fetch([]), do: 0
@impl true
def handle_info({:chain_event, :token_transfers, _type, token_transfers}, state) do
token_transfers
|> Enum.map(& &1.token_contract_address_hash)
|> Enum.each(&insert_or_update_counter(&1, 1))
{:noreply, state}
end
@doc """
Inserts a new item into the `:ets` table.
When the record exist, the counter will be incremented by one. When the
record does not exist, the counter will be inserted with a default value.
"""
@spec insert_or_update_counter(Hash.t(), non_neg_integer) :: term()
def insert_or_update_counter(token_hash, number) do
default = {to_string(token_hash), 0}
:ets.update_counter(table_name(), to_string(token_hash), number, default)
end
end

@ -112,17 +112,16 @@ defmodule Explorer.Chain.TokenTransferTest do
end end
end end
describe "count_token_transfers/1" do describe "count_token_transfers/0" do
test "counts how many token transfers a token has" do test "returns token transfers grouped by tokens" do
token_contract_address = insert(:contract_address) token_contract_address = insert(:contract_address)
token = insert(:token, contract_address: token_contract_address)
transaction = transaction =
:transaction :transaction
|> insert() |> insert()
|> with_block() |> with_block()
token = insert(:token)
insert( insert(
:token_transfer, :token_transfer,
to_address: build(:address), to_address: build(:address),
@ -139,7 +138,10 @@ defmodule Explorer.Chain.TokenTransferTest do
token: token token: token
) )
assert TokenTransfer.count_token_transfers_from_token_hash(token_contract_address.hash) == 2 results = TokenTransfer.count_token_transfers()
assert length(results) == 1
assert List.first(results) == {token.contract_address_hash, 2}
end end
end end

@ -0,0 +1,50 @@
defmodule Explorer.Counters.TokenTransferCounterTest do
use Explorer.DataCase
alias Explorer.Counters.TokenTransferCounter
describe "consolidate/0" do
test "loads the token's transfers consolidate info" do
token_contract_address = insert(:contract_address)
token = insert(:token, contract_address: token_contract_address)
transaction =
:transaction
|> insert()
|> with_block()
insert(
:token_transfer,
to_address: build(:address),
transaction: transaction,
token_contract_address: token_contract_address,
token: token
)
insert(
:token_transfer,
to_address: build(:address),
transaction: transaction,
token_contract_address: token_contract_address,
token: token
)
TokenTransferCounter.consolidate()
assert TokenTransferCounter.fetch(token.contract_address_hash) == 2
end
end
describe "fetch/1" do
test "fetchs the total token transfers by token contract address hash" do
token_contract_address = insert(:contract_address)
token = insert(:token, contract_address: token_contract_address)
assert TokenTransferCounter.fetch(token.contract_address_hash) == 0
TokenTransferCounter.insert_or_update_counter(token.contract_address_hash, 15)
assert TokenTransferCounter.fetch(token.contract_address_hash) == 15
end
end
end
Loading…
Cancel
Save