Merge pull request #3470 from poanetwork/vb-address-tokens-sum
Display sum of tokens' USD value at tokens holder's address pagepull/3479/head
commit
52563b5a1c
@ -0,0 +1,132 @@ |
||||
defmodule Explorer.Chain.Cache.TokenExchangeRate do |
||||
@moduledoc """ |
||||
Caches Token USD exchange_rate. |
||||
""" |
||||
use GenServer |
||||
|
||||
alias Explorer.ExchangeRates.Source |
||||
|
||||
@cache_name :token_exchange_rate |
||||
@last_update_key "last_update" |
||||
@cache_period Application.get_env(:explorer, __MODULE__)[:period] |
||||
|
||||
@ets_opts [ |
||||
:set, |
||||
:named_table, |
||||
:public, |
||||
read_concurrency: true |
||||
] |
||||
|
||||
config = Application.get_env(:explorer, Explorer.Chain.Cache.TokenExchangeRate) |
||||
@enable_consolidation Keyword.get(config, :enable_consolidation) |
||||
|
||||
@spec start_link(term()) :: GenServer.on_start() |
||||
def start_link(_) do |
||||
GenServer.start_link(__MODULE__, :ok, name: __MODULE__) |
||||
end |
||||
|
||||
@impl true |
||||
def init(_args) do |
||||
create_cache_table() |
||||
|
||||
{:ok, %{consolidate?: enable_consolidation?()}, {:continue, :ok}} |
||||
end |
||||
|
||||
@impl true |
||||
def handle_continue(:ok, %{consolidate?: true} = state) do |
||||
{:noreply, state} |
||||
end |
||||
|
||||
@impl true |
||||
def handle_continue(:ok, state) do |
||||
{:noreply, state} |
||||
end |
||||
|
||||
@impl true |
||||
def handle_info(:consolidate, state) do |
||||
{:noreply, state} |
||||
end |
||||
|
||||
def cache_key(symbol) do |
||||
"token_symbol_exchange_rate_#{symbol}" |
||||
end |
||||
|
||||
def fetch(symbol) do |
||||
if cache_expired?(symbol) || value_is_empty?(symbol) do |
||||
Task.start_link(fn -> |
||||
update_cache(symbol) |
||||
end) |
||||
end |
||||
|
||||
fetch_from_cache(cache_key(symbol)) |
||||
end |
||||
|
||||
def cache_name, do: @cache_name |
||||
|
||||
defp cache_expired?(symbol) do |
||||
updated_at = fetch_from_cache("#{cache_key(symbol)}_#{@last_update_key}") |
||||
|
||||
cond do |
||||
is_nil(updated_at) -> true |
||||
current_time() - updated_at > @cache_period -> true |
||||
true -> false |
||||
end |
||||
end |
||||
|
||||
defp value_is_empty?(symbol) do |
||||
value = fetch_from_cache(cache_key(symbol)) |
||||
is_nil(value) || value == 0 |
||||
end |
||||
|
||||
defp update_cache(symbol) do |
||||
put_into_cache("#{cache_key(symbol)}_#{@last_update_key}", current_time()) |
||||
|
||||
exchange_rate = fetch_token_exchange_rate(symbol) |
||||
|
||||
put_into_cache(cache_key(symbol), exchange_rate) |
||||
end |
||||
|
||||
def fetch_token_exchange_rate(symbol) do |
||||
case Source.fetch_exchange_rates_for_token(symbol) do |
||||
{:ok, [rates]} -> |
||||
rates.usd_value |
||||
|
||||
_ -> |
||||
nil |
||||
end |
||||
end |
||||
|
||||
defp fetch_from_cache(key) do |
||||
case :ets.lookup(@cache_name, key) do |
||||
[{_, value}] -> |
||||
value |
||||
|
||||
[] -> |
||||
0 |
||||
end |
||||
end |
||||
|
||||
def put_into_cache(key, value) do |
||||
if cache_table_exists?() do |
||||
:ets.insert(@cache_name, {key, value}) |
||||
end |
||||
end |
||||
|
||||
defp current_time do |
||||
utc_now = DateTime.utc_now() |
||||
|
||||
DateTime.to_unix(utc_now, :millisecond) |
||||
end |
||||
|
||||
def cache_table_exists? do |
||||
:ets.whereis(@cache_name) !== :undefined |
||||
end |
||||
|
||||
def create_cache_table do |
||||
unless cache_table_exists?() do |
||||
:ets.new(@cache_name, @ets_opts) |
||||
end |
||||
end |
||||
|
||||
def enable_consolidation?, do: @enable_consolidation |
||||
end |
@ -0,0 +1,12 @@ |
||||
defmodule Explorer.Chain.CurrencyHelpers do |
||||
@moduledoc """ |
||||
Helper functions for interacting with `t:BlockScoutWeb.ExchangeRates.USD.t/0` values. |
||||
""" |
||||
|
||||
@spec divide_decimals(Decimal.t(), Decimal.t()) :: Decimal.t() |
||||
def divide_decimals(%{sign: sign, coef: coef, exp: exp}, decimals) do |
||||
sign |
||||
|> Decimal.new(coef, exp - Decimal.to_integer(decimals)) |
||||
|> Decimal.normalize() |
||||
end |
||||
end |
@ -0,0 +1,112 @@ |
||||
defmodule Explorer.Counters.AddressTokenUsdSum do |
||||
@moduledoc """ |
||||
Caches Address tokens USD value. |
||||
""" |
||||
use GenServer |
||||
|
||||
alias Explorer.Chain |
||||
|
||||
@cache_name :address_tokens_usd_value |
||||
@last_update_key "last_update" |
||||
@cache_period Application.get_env(:explorer, __MODULE__)[:period] |
||||
|
||||
@ets_opts [ |
||||
:set, |
||||
:named_table, |
||||
:public, |
||||
read_concurrency: true |
||||
] |
||||
|
||||
config = Application.get_env(:explorer, Explorer.Counters.AddressTokenUsdSum) |
||||
@enable_consolidation Keyword.get(config, :enable_consolidation) |
||||
|
||||
@spec start_link(term()) :: GenServer.on_start() |
||||
def start_link(_) do |
||||
GenServer.start_link(__MODULE__, :ok, name: __MODULE__) |
||||
end |
||||
|
||||
@impl true |
||||
def init(_args) do |
||||
create_cache_table() |
||||
|
||||
{:ok, %{consolidate?: enable_consolidation?()}, {:continue, :ok}} |
||||
end |
||||
|
||||
@impl true |
||||
def handle_continue(:ok, %{consolidate?: true} = state) do |
||||
{:noreply, state} |
||||
end |
||||
|
||||
@impl true |
||||
def handle_continue(:ok, state) do |
||||
{:noreply, state} |
||||
end |
||||
|
||||
@impl true |
||||
def handle_info(:consolidate, state) do |
||||
{:noreply, state} |
||||
end |
||||
|
||||
def fetch(address_hash, token_balances) do |
||||
if cache_expired?(address_hash) do |
||||
Task.start_link(fn -> |
||||
update_cache(address_hash, token_balances) |
||||
end) |
||||
end |
||||
|
||||
address_hash_string = get_address_hash_string(address_hash) |
||||
fetch_from_cache("hash_#{address_hash_string}") |
||||
end |
||||
|
||||
def cache_name, do: @cache_name |
||||
|
||||
defp cache_expired?(address_hash) do |
||||
address_hash_string = get_address_hash_string(address_hash) |
||||
updated_at = fetch_from_cache("hash_#{address_hash_string}_#{@last_update_key}") |
||||
|
||||
cond do |
||||
is_nil(updated_at) -> true |
||||
current_time() - updated_at > @cache_period -> true |
||||
true -> false |
||||
end |
||||
end |
||||
|
||||
defp update_cache(address_hash, token_balances) do |
||||
address_hash_string = get_address_hash_string(address_hash) |
||||
put_into_cache("hash_#{address_hash_string}_#{@last_update_key}", current_time()) |
||||
new_data = Chain.address_tokens_usd_sum(token_balances) |
||||
put_into_cache("hash_#{address_hash_string}", new_data) |
||||
end |
||||
|
||||
defp fetch_from_cache(key) do |
||||
case :ets.lookup(@cache_name, key) do |
||||
[{_, value}] -> |
||||
value |
||||
|
||||
[] -> |
||||
0 |
||||
end |
||||
end |
||||
|
||||
defp put_into_cache(key, value) do |
||||
:ets.insert(@cache_name, {key, value}) |
||||
end |
||||
|
||||
defp get_address_hash_string(address_hash) do |
||||
Base.encode16(address_hash.bytes, case: :lower) |
||||
end |
||||
|
||||
defp current_time do |
||||
utc_now = DateTime.utc_now() |
||||
|
||||
DateTime.to_unix(utc_now, :millisecond) |
||||
end |
||||
|
||||
def create_cache_table do |
||||
if :ets.whereis(@cache_name) == :undefined do |
||||
:ets.new(@cache_name, @ets_opts) |
||||
end |
||||
end |
||||
|
||||
def enable_consolidation?, do: @enable_consolidation |
||||
end |
Loading…
Reference in new issue