commit
55a5c651d5
@ -0,0 +1,49 @@ |
|||||||
|
defmodule BlockScoutWeb.API.V1.HealthController do |
||||||
|
use BlockScoutWeb, :controller |
||||||
|
|
||||||
|
alias Explorer.Chain |
||||||
|
|
||||||
|
def health(conn, _) do |
||||||
|
with {:ok, number, timestamp} <- Chain.last_block_status() do |
||||||
|
send_resp(conn, :ok, result(number, timestamp)) |
||||||
|
else |
||||||
|
status -> send_resp(conn, :internal_server_error, error(status)) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
def result(number, timestamp) do |
||||||
|
%{ |
||||||
|
"healthy" => true, |
||||||
|
"data" => %{ |
||||||
|
"latest_block_number" => to_string(number), |
||||||
|
"latest_block_inserted_at" => to_string(timestamp) |
||||||
|
} |
||||||
|
} |
||||||
|
|> Jason.encode!() |
||||||
|
end |
||||||
|
|
||||||
|
def error({:error, :no_blocks}) do |
||||||
|
%{ |
||||||
|
"healthy" => false, |
||||||
|
"error_code" => 5002, |
||||||
|
"error_title" => "no blocks in db", |
||||||
|
"error_description" => "There are no blocks in the DB" |
||||||
|
} |
||||||
|
|> Jason.encode!() |
||||||
|
end |
||||||
|
|
||||||
|
def error({:error, number, timestamp}) do |
||||||
|
%{ |
||||||
|
"healthy" => false, |
||||||
|
"error_code" => 5001, |
||||||
|
"error_title" => "blocks fetching is stuck", |
||||||
|
"error_description" => |
||||||
|
"There are no new blocks in the DB for the last 5 mins. Check the healthiness of Ethereum archive node or the Blockscout DB instance", |
||||||
|
"data" => %{ |
||||||
|
"latest_block_number" => to_string(number), |
||||||
|
"latest_block_inserted_at" => to_string(timestamp) |
||||||
|
} |
||||||
|
} |
||||||
|
|> Jason.encode!() |
||||||
|
end |
||||||
|
end |
@ -1,51 +1,39 @@ |
|||||||
<div class="tile"> |
<tr> |
||||||
<div class="row"> |
<td class="stakes-td"> |
||||||
<!-- rank --> |
|
||||||
<div class="col-2 col-md-1 d-flex justify-content-center align-items-center"> |
|
||||||
<!-- incremented number by order in the list --> |
<!-- incremented number by order in the list --> |
||||||
<span> |
<span class="color-lighten"> |
||||||
<%= @index %> |
<%= @index %> |
||||||
</span> |
</span> |
||||||
</div> |
</td> |
||||||
|
<td class="stakes-td"> |
||||||
<div class="col-10 col-md-11"> |
<%= @address |> BlockScoutWeb.AddressView.address_partial_selector(nil, nil) |> BlockScoutWeb.RenderHelpers.render_partial() %> |
||||||
<div class="row"> |
</td> |
||||||
<div class="col-md-6 d-flex flex-column"> |
<td class="stakes-td"> |
||||||
<%= @address |> BlockScoutWeb.AddressView.address_partial_selector(nil, nil) |> BlockScoutWeb.RenderHelpers.render_partial() %> |
<span data-test="address_balance"><%= balance(@address) %></span> |
||||||
<!-- number of txns for this address --> |
<!-- USD value of the balance --> |
||||||
<span class="mr-4"> |
<span |
||||||
<span data-test="transaction_count"> |
data-wei-value="<%= if @address.fetched_coin_balance, do: @address.fetched_coin_balance.value %>" |
||||||
<%= @tx_count %> |
<% if !empty_exchange_rate?(@exchange_rate) do %> |
||||||
</span> <%= gettext "Transactions sent" %> |
data-usd-exchange-rate="<%= @exchange_rate.usd_value %>"> |
||||||
<% if validator?(@address) do %> |
<% end %> |
||||||
<span data-test="validation_count"> |
</span> |
||||||
<%= @validation_count %> |
</td> |
||||||
</span> <%= gettext "Validations" %> |
<td class="stakes-td color-lighten"> |
||||||
<% end %> |
<!-- percentage of coins from total supply --> |
||||||
</span> |
<%= if @total_supply do %> |
||||||
</div> |
(<%= balance_percentage(@address, @total_supply) %>) |
||||||
|
<% end %> |
||||||
<!-- balance and percentage --> |
</td> |
||||||
<div class="col-md-6 d-flex flex-column text-md-right mt-3 mt-md-0"> |
<td class="stakes-td"> |
||||||
<!-- address coin balance --> |
<span class="mr-4"> |
||||||
<span class="tile-title" data-test="address_balance"><%= balance(@address) %></span> |
<span data-test="transaction_count"> |
||||||
<div class="d-flex flex-column flex-md-row justify-content-md-end"> |
<%= @tx_count %> |
||||||
<!-- USD value of the balance --> |
</span> <%= gettext "Transactions sent" %> |
||||||
<span |
<% if validator?(@address) do %> |
||||||
data-wei-value="<%= if @address.fetched_coin_balance, do: @address.fetched_coin_balance.value %>" |
<span data-test="validation_count"> |
||||||
<% if !empty_exchange_rate?(@exchange_rate) do %> |
<%= @validation_count %> |
||||||
data-usd-exchange-rate="<%= @exchange_rate.usd_value %>"> |
</span> <%= gettext "Validations" %> |
||||||
<% end %> |
<% end %> |
||||||
</span> |
</span> |
||||||
<!-- percentage of coins from total supply --> |
</td> |
||||||
<span class="ml-0 ml-md-2"> |
</tr> |
||||||
<%= if @total_supply do %> |
|
||||||
(<%= balance_percentage(@address, @total_supply) %>) |
|
||||||
<% end %> |
|
||||||
</span> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
@ -0,0 +1,50 @@ |
|||||||
|
defmodule BlockScoutWeb.API.V1.HealthControllerTest do |
||||||
|
use BlockScoutWeb.ConnCase |
||||||
|
|
||||||
|
describe "GET last_block_status/0" do |
||||||
|
test "returns error when there are no blocks in db", %{conn: conn} do |
||||||
|
request = get(conn, api_v1_health_path(conn, :health)) |
||||||
|
|
||||||
|
assert request.status == 500 |
||||||
|
|
||||||
|
assert request.resp_body == |
||||||
|
"{\"error_code\":5002,\"error_description\":\"There are no blocks in the DB\",\"error_title\":\"no blocks in db\",\"healthy\":false}" |
||||||
|
end |
||||||
|
|
||||||
|
test "returns error when last block is stale", %{conn: conn} do |
||||||
|
insert(:block, consensus: true, timestamp: Timex.shift(DateTime.utc_now(), hours: -50)) |
||||||
|
|
||||||
|
request = get(conn, api_v1_health_path(conn, :health)) |
||||||
|
|
||||||
|
assert request.status == 500 |
||||||
|
|
||||||
|
assert %{ |
||||||
|
"healthy" => false, |
||||||
|
"error_code" => 5001, |
||||||
|
"error_title" => "blocks fetching is stuck", |
||||||
|
"error_description" => |
||||||
|
"There are no new blocks in the DB for the last 5 mins. Check the healthiness of Ethereum archive node or the Blockscout DB instance", |
||||||
|
"data" => %{ |
||||||
|
"latest_block_number" => _, |
||||||
|
"latest_block_inserted_at" => _ |
||||||
|
} |
||||||
|
} = Poison.decode!(request.resp_body) |
||||||
|
end |
||||||
|
|
||||||
|
test "returns ok when last block is not stale", %{conn: conn} do |
||||||
|
insert(:block, consensus: true, timestamp: DateTime.utc_now()) |
||||||
|
|
||||||
|
request = get(conn, api_v1_health_path(conn, :health)) |
||||||
|
|
||||||
|
assert request.status == 200 |
||||||
|
|
||||||
|
assert %{ |
||||||
|
"healthy" => true, |
||||||
|
"data" => %{ |
||||||
|
"latest_block_number" => _, |
||||||
|
"latest_block_inserted_at" => _ |
||||||
|
} |
||||||
|
} = Poison.decode!(request.resp_body) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -1,78 +0,0 @@ |
|||||||
defmodule Explorer.Chain.Supply.TransactionAndLog do |
|
||||||
@moduledoc """ |
|
||||||
Defines the supply API for calculating the supply for smaller chains with |
|
||||||
specific mint and burn events |
|
||||||
""" |
|
||||||
use Explorer.Chain.Supply |
|
||||||
|
|
||||||
alias Explorer.Chain.{InternalTransaction, Log, Wei} |
|
||||||
alias Explorer.{Chain, Repo} |
|
||||||
|
|
||||||
{:ok, base_wei} = Wei.cast(0) |
|
||||||
@base_wei base_wei |
|
||||||
|
|
||||||
{:ok, burn_address} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000") |
|
||||||
|
|
||||||
@burn_address burn_address |
|
||||||
@bridge_edge "0x3c798bbcf33115b42c728b8504cff11dd58736e9fa789f1cda2738db7d696b2a" |
|
||||||
|
|
||||||
import Ecto.Query, only: [from: 2] |
|
||||||
|
|
||||||
def circulating, do: total(Timex.now()) |
|
||||||
|
|
||||||
def total, do: total(Timex.now()) |
|
||||||
|
|
||||||
@doc false |
|
||||||
@spec total(DateTime.t()) :: %Decimal{sign: 1} |
|
||||||
def total(on_date) do |
|
||||||
on_date |
|
||||||
|> minted_value |
|
||||||
|> Wei.sub(burned_value(on_date)) |
|
||||||
|> Wei.to(:ether) |
|
||||||
end |
|
||||||
|
|
||||||
def supply_for_days(days_count) when is_integer(days_count) and days_count > 0 do |
|
||||||
past_days = -(days_count - 1) |
|
||||||
|
|
||||||
result = |
|
||||||
for i <- past_days..0, into: %{} do |
|
||||||
datetime = Timex.shift(Timex.now(), days: i) |
|
||||||
{DateTime.to_date(datetime), total(datetime)} |
|
||||||
end |
|
||||||
|
|
||||||
{:ok, result} |
|
||||||
end |
|
||||||
|
|
||||||
defp minted_value(on_date) do |
|
||||||
query = |
|
||||||
from( |
|
||||||
l in Log, |
|
||||||
join: t in assoc(l, :transaction), |
|
||||||
join: b in assoc(t, :block), |
|
||||||
where: b.timestamp <= ^on_date and l.first_topic == @bridge_edge, |
|
||||||
select: fragment("concat('0x', encode(?, 'hex'))", l.data) |
|
||||||
) |
|
||||||
|
|
||||||
query |
|
||||||
|> Repo.all() |
|
||||||
|> Enum.reduce(@base_wei, fn data, acc -> |
|
||||||
{:ok, wei_value} = Wei.cast(data) |
|
||||||
Wei.sum(wei_value, acc) |
|
||||||
end) |
|
||||||
end |
|
||||||
|
|
||||||
defp burned_value(on_date) do |
|
||||||
query = |
|
||||||
from( |
|
||||||
it in InternalTransaction, |
|
||||||
join: t in assoc(it, :transaction), |
|
||||||
join: b in assoc(t, :block), |
|
||||||
where: b.timestamp <= ^on_date and it.to_address_hash == ^@burn_address, |
|
||||||
select: it.value |
|
||||||
) |
|
||||||
|
|
||||||
query |
|
||||||
|> Repo.all() |
|
||||||
|> Enum.reduce(@base_wei, fn data, acc -> Wei.sum(data, acc) end) |
|
||||||
end |
|
||||||
end |
|
@ -0,0 +1,143 @@ |
|||||||
|
defmodule Explorer.Chain.TransactionsCache do |
||||||
|
@moduledoc """ |
||||||
|
Caches the latest imported transactions |
||||||
|
""" |
||||||
|
|
||||||
|
alias Explorer.Chain.Transaction |
||||||
|
alias Explorer.Repo |
||||||
|
|
||||||
|
@transactions_ids_key "transactions_ids" |
||||||
|
@cache_name :transactions |
||||||
|
@max_size 51 |
||||||
|
@preloads [ |
||||||
|
:block, |
||||||
|
created_contract_address: :names, |
||||||
|
from_address: :names, |
||||||
|
to_address: :names, |
||||||
|
token_transfers: :token, |
||||||
|
token_transfers: :from_address, |
||||||
|
token_transfers: :to_address |
||||||
|
] |
||||||
|
|
||||||
|
@spec cache_name :: atom() |
||||||
|
def cache_name, do: @cache_name |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Fetches a transaction from its id ({block_number, index}), returns nil if not found |
||||||
|
""" |
||||||
|
@spec get({non_neg_integer(), non_neg_integer()}) :: Transaction.t() | nil |
||||||
|
def get(id), do: ConCache.get(@cache_name, id) |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Return the current number of transactions stored |
||||||
|
""" |
||||||
|
@spec size :: non_neg_integer() |
||||||
|
def size, do: Enum.count(transactions_ids()) |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Checks if there are enough transactions stored |
||||||
|
""" |
||||||
|
@spec enough?(non_neg_integer()) :: boolean() |
||||||
|
def enough?(amount) do |
||||||
|
amount <= size() |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Checks if the number of transactions stored is already the max allowed |
||||||
|
""" |
||||||
|
@spec full? :: boolean() |
||||||
|
def full? do |
||||||
|
@max_size <= size() |
||||||
|
end |
||||||
|
|
||||||
|
@doc "Returns the list ids of the transactions currently stored" |
||||||
|
@spec transactions_ids :: [{non_neg_integer(), non_neg_integer()}] |
||||||
|
def transactions_ids do |
||||||
|
ConCache.get(@cache_name, @transactions_ids_key) || [] |
||||||
|
end |
||||||
|
|
||||||
|
@doc "Returns all the stored transactions" |
||||||
|
@spec all :: [Transaction.t()] |
||||||
|
def all, do: Enum.map(transactions_ids(), &get(&1)) |
||||||
|
|
||||||
|
@doc "Returns the `n` most recent transactions stored" |
||||||
|
@spec take(integer()) :: [Transaction.t()] |
||||||
|
def take(amount) do |
||||||
|
transactions_ids() |
||||||
|
|> Enum.take(amount) |
||||||
|
|> Enum.map(&get(&1)) |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Returns the `n` most recent transactions, unless there are not as many stored, |
||||||
|
in which case returns `nil` |
||||||
|
""" |
||||||
|
@spec take_enough(integer()) :: [Transaction.t()] | nil |
||||||
|
def take_enough(amount) do |
||||||
|
if enough?(amount), do: take(amount) |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Adds a transaction (or a list of transactions). |
||||||
|
If the cache is already full, the transaction will be only stored if it can take |
||||||
|
the place of a less recent one. |
||||||
|
NOTE: each transaction is inserted atomically |
||||||
|
""" |
||||||
|
@spec update([Transaction.t()] | Transaction.t() | nil) :: :ok |
||||||
|
def update(transactions) when is_nil(transactions), do: :ok |
||||||
|
|
||||||
|
def update(transactions) when is_list(transactions) do |
||||||
|
Enum.map(transactions, &update(&1)) |
||||||
|
end |
||||||
|
|
||||||
|
def update(transaction) do |
||||||
|
ConCache.isolated(@cache_name, @transactions_ids_key, fn -> |
||||||
|
transaction_id = {transaction.block_number, transaction.index} |
||||||
|
ids = transactions_ids() |
||||||
|
|
||||||
|
if full?() do |
||||||
|
{init, [min]} = Enum.split(ids, -1) |
||||||
|
|
||||||
|
cond do |
||||||
|
transaction_id < min -> |
||||||
|
:ok |
||||||
|
|
||||||
|
transaction_id > min -> |
||||||
|
insert_transaction(transaction_id, transaction, init) |
||||||
|
ConCache.delete(@cache_name, min) |
||||||
|
|
||||||
|
transaction_id == min -> |
||||||
|
put_transaction(transaction_id, transaction) |
||||||
|
end |
||||||
|
else |
||||||
|
insert_transaction(transaction_id, transaction, ids) |
||||||
|
end |
||||||
|
end) |
||||||
|
end |
||||||
|
|
||||||
|
defp insert_transaction(transaction_id, transaction, ids) do |
||||||
|
put_transaction(transaction_id, transaction) |
||||||
|
|
||||||
|
ConCache.put(@cache_name, @transactions_ids_key, insert_sorted(transaction_id, ids)) |
||||||
|
end |
||||||
|
|
||||||
|
defp put_transaction(transaction_id, transaction) do |
||||||
|
full_transaction = Repo.preload(transaction, @preloads) |
||||||
|
|
||||||
|
ConCache.put(@cache_name, transaction_id, full_transaction) |
||||||
|
end |
||||||
|
|
||||||
|
defp insert_sorted(id, ids) do |
||||||
|
case ids do |
||||||
|
[] -> |
||||||
|
[id] |
||||||
|
|
||||||
|
[head | tail] -> |
||||||
|
cond do |
||||||
|
head > id -> [head | insert_sorted(id, tail)] |
||||||
|
head < id -> [id | ids] |
||||||
|
head == id -> ids |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -1,6 +1,6 @@ |
|||||||
defmodule Explorer.ExchangeRates.Source.TransactionAndLog do |
defmodule Explorer.ExchangeRates.Source.TokenBridge do |
||||||
@moduledoc """ |
@moduledoc """ |
||||||
Adapter for calculating the market cap and total supply from logs and transactions |
Adapter for calculating the market cap and total supply from token bridge |
||||||
while still getting other info like price in dollars and bitcoin from a secondary source |
while still getting other info like price in dollars and bitcoin from a secondary source |
||||||
""" |
""" |
||||||
|
|
@ -1,122 +0,0 @@ |
|||||||
defmodule Explorer.Chain.Supply.TransactionAndLogTest do |
|
||||||
use Explorer.DataCase |
|
||||||
alias Explorer.Chain |
|
||||||
alias Explorer.Chain.Supply.TransactionAndLog |
|
||||||
|
|
||||||
setup do |
|
||||||
{:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000") |
|
||||||
|
|
||||||
burn_address = |
|
||||||
case Chain.hash_to_address(burn_address_hash) do |
|
||||||
{:ok, burn_address} -> burn_address |
|
||||||
{:error, :not_found} -> insert(:address, hash: "0x0000000000000000000000000000000000000000") |
|
||||||
end |
|
||||||
|
|
||||||
{:ok, %{burn_address: burn_address}} |
|
||||||
end |
|
||||||
|
|
||||||
describe "total/1" do |
|
||||||
test "today with no mints or burns brings zero" do |
|
||||||
assert TransactionAndLog.total(Timex.now()) == Decimal.new(0) |
|
||||||
end |
|
||||||
|
|
||||||
test "today with mints and burns calculates a value", %{burn_address: burn_address} do |
|
||||||
old_block = insert(:block, timestamp: Timex.shift(Timex.now(), days: -1), number: 1000) |
|
||||||
|
|
||||||
insert(:log, |
|
||||||
transaction: |
|
||||||
insert(:transaction, block: old_block, block_number: 1000, cumulative_gas_used: 1, gas_used: 1, index: 2), |
|
||||||
first_topic: "0x3c798bbcf33115b42c728b8504cff11dd58736e9fa789f1cda2738db7d696b2a", |
|
||||||
data: "0x0000000000000000000000000000000000000000000000008ac7230489e80000" |
|
||||||
) |
|
||||||
|
|
||||||
insert(:internal_transaction, |
|
||||||
index: 527, |
|
||||||
transaction: |
|
||||||
insert(:transaction, block: old_block, block_number: 1000, cumulative_gas_used: 1, gas_used: 1, index: 3), |
|
||||||
to_address: burn_address, |
|
||||||
value: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000" |
|
||||||
) |
|
||||||
|
|
||||||
assert TransactionAndLog.total(Timex.now()) == Decimal.new(9) |
|
||||||
end |
|
||||||
|
|
||||||
test "yesterday with mints and burns calculates a value ignoring whatever happened today", %{ |
|
||||||
burn_address: burn_address |
|
||||||
} do |
|
||||||
old_block = insert(:block, timestamp: Timex.shift(Timex.now(), days: -1), number: 1000) |
|
||||||
|
|
||||||
insert(:log, |
|
||||||
transaction: |
|
||||||
insert(:transaction, block: old_block, block_number: 1000, cumulative_gas_used: 1, gas_used: 1, index: 2), |
|
||||||
first_topic: "0x3c798bbcf33115b42c728b8504cff11dd58736e9fa789f1cda2738db7d696b2a", |
|
||||||
data: "0x0000000000000000000000000000000000000000000000008ac7230489e80000" |
|
||||||
) |
|
||||||
|
|
||||||
new_block = insert(:block, timestamp: Timex.now(), number: 1001) |
|
||||||
|
|
||||||
insert(:internal_transaction, |
|
||||||
index: 527, |
|
||||||
transaction: |
|
||||||
insert(:transaction, block: new_block, block_number: 1000, cumulative_gas_used: 1, gas_used: 1, index: 3), |
|
||||||
to_address: burn_address, |
|
||||||
value: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000" |
|
||||||
) |
|
||||||
|
|
||||||
assert TransactionAndLog.total(Timex.shift(Timex.now(), days: -1)) == Decimal.new(10) |
|
||||||
end |
|
||||||
end |
|
||||||
|
|
||||||
describe "total/0" do |
|
||||||
test "calculates the same value as total/1 receiving today's date", %{burn_address: burn_address} do |
|
||||||
old_block = insert(:block, timestamp: Timex.shift(Timex.now(), days: -1), number: 1000) |
|
||||||
|
|
||||||
insert(:log, |
|
||||||
transaction: |
|
||||||
insert(:transaction, block: old_block, block_number: 1000, cumulative_gas_used: 1, gas_used: 1, index: 2), |
|
||||||
first_topic: "0x3c798bbcf33115b42c728b8504cff11dd58736e9fa789f1cda2738db7d696b2a", |
|
||||||
data: "0x0000000000000000000000000000000000000000000000008ac7230489e80000" |
|
||||||
) |
|
||||||
|
|
||||||
insert(:internal_transaction, |
|
||||||
index: 527, |
|
||||||
transaction: |
|
||||||
insert(:transaction, block: old_block, block_number: 1000, cumulative_gas_used: 1, gas_used: 1, index: 3), |
|
||||||
to_address: burn_address, |
|
||||||
value: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000" |
|
||||||
) |
|
||||||
|
|
||||||
assert TransactionAndLog.total() == TransactionAndLog.total(Timex.now()) |
|
||||||
end |
|
||||||
end |
|
||||||
|
|
||||||
describe "supply_for_days/1" do |
|
||||||
test "bring the supply of today and yesterday when receiving 2", %{burn_address: burn_address} do |
|
||||||
old_block = insert(:block, timestamp: Timex.shift(Timex.now(), days: -1), number: 1000) |
|
||||||
|
|
||||||
insert(:log, |
|
||||||
transaction: |
|
||||||
insert(:transaction, block: old_block, block_number: 1000, cumulative_gas_used: 1, gas_used: 1, index: 2), |
|
||||||
first_topic: "0x3c798bbcf33115b42c728b8504cff11dd58736e9fa789f1cda2738db7d696b2a", |
|
||||||
data: "0x0000000000000000000000000000000000000000000000008ac7230489e80000" |
|
||||||
) |
|
||||||
|
|
||||||
new_block = insert(:block, timestamp: Timex.now(), number: 1001) |
|
||||||
|
|
||||||
insert(:internal_transaction, |
|
||||||
index: 527, |
|
||||||
transaction: |
|
||||||
insert(:transaction, block: new_block, block_number: 1000, cumulative_gas_used: 1, gas_used: 1, index: 3), |
|
||||||
to_address: burn_address, |
|
||||||
value: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000" |
|
||||||
) |
|
||||||
|
|
||||||
expected_result = %{ |
|
||||||
Timex.shift(Timex.today(), days: -1) => Decimal.new(10), |
|
||||||
Timex.today() => Decimal.new(9) |
|
||||||
} |
|
||||||
|
|
||||||
assert TransactionAndLog.supply_for_days(2) == {:ok, expected_result} |
|
||||||
end |
|
||||||
end |
|
||||||
end |
|
@ -0,0 +1,95 @@ |
|||||||
|
defmodule Explorer.Chain.TransactionsCacheTest do |
||||||
|
use Explorer.DataCase |
||||||
|
|
||||||
|
alias Explorer.Chain.TransactionsCache |
||||||
|
alias Explorer.Repo |
||||||
|
|
||||||
|
@size 51 |
||||||
|
|
||||||
|
describe "update/1" do |
||||||
|
test "adds a new value to a new cache with preloads" do |
||||||
|
transaction = insert(:transaction) |> preload_all() |
||||||
|
|
||||||
|
TransactionsCache.update(transaction) |
||||||
|
|
||||||
|
assert TransactionsCache.take(1) == [transaction] |
||||||
|
end |
||||||
|
|
||||||
|
test "adds several elements, removing the oldest when necessary" do |
||||||
|
transactions = |
||||||
|
1..@size |
||||||
|
|> Enum.map(fn n -> |
||||||
|
block = insert(:block, number: n) |
||||||
|
insert(:transaction) |> with_block(block) |
||||||
|
end) |
||||||
|
|
||||||
|
TransactionsCache.update(transactions) |
||||||
|
|
||||||
|
assert TransactionsCache.all() == Enum.reverse(preload_all(transactions)) |
||||||
|
|
||||||
|
more_transactions = |
||||||
|
(@size + 1)..(@size + 10) |
||||||
|
|> Enum.map(fn n -> |
||||||
|
block = insert(:block, number: n) |
||||||
|
insert(:transaction) |> with_block(block) |
||||||
|
end) |
||||||
|
|
||||||
|
TransactionsCache.update(more_transactions) |
||||||
|
|
||||||
|
kept_transactions = |
||||||
|
Enum.reverse(transactions ++ more_transactions) |
||||||
|
|> Enum.take(@size) |
||||||
|
|> preload_all() |
||||||
|
|
||||||
|
assert TransactionsCache.take(@size) == kept_transactions |
||||||
|
end |
||||||
|
|
||||||
|
test "does not add a transaction too old when full" do |
||||||
|
transactions = |
||||||
|
10..(@size + 9) |
||||||
|
|> Enum.map(fn n -> |
||||||
|
block = insert(:block, number: n) |
||||||
|
insert(:transaction) |> with_block(block) |
||||||
|
end) |
||||||
|
|
||||||
|
TransactionsCache.update(transactions) |
||||||
|
|
||||||
|
loaded_transactions = Enum.reverse(preload_all(transactions)) |
||||||
|
assert TransactionsCache.all() == loaded_transactions |
||||||
|
|
||||||
|
block = insert(:block, number: 1) |
||||||
|
insert(:transaction) |> with_block(block) |> TransactionsCache.update() |
||||||
|
|
||||||
|
assert TransactionsCache.all() == loaded_transactions |
||||||
|
end |
||||||
|
|
||||||
|
test "adds intermediate transactions" do |
||||||
|
blocks = 1..10 |> Map.new(fn n -> {n, insert(:block, number: n)} end) |
||||||
|
|
||||||
|
insert(:transaction) |> with_block(blocks[1]) |> TransactionsCache.update() |
||||||
|
insert(:transaction) |> with_block(blocks[10]) |> TransactionsCache.update() |
||||||
|
|
||||||
|
assert TransactionsCache.size() == 2 |
||||||
|
|
||||||
|
insert(:transaction) |> with_block(blocks[5]) |> TransactionsCache.update() |
||||||
|
|
||||||
|
assert TransactionsCache.size() == 3 |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
defp preload_all(transactions) when is_list(transactions) do |
||||||
|
Enum.map(transactions, &preload_all(&1)) |
||||||
|
end |
||||||
|
|
||||||
|
defp preload_all(transaction) do |
||||||
|
Repo.preload(transaction, [ |
||||||
|
:block, |
||||||
|
created_contract_address: :names, |
||||||
|
from_address: :names, |
||||||
|
to_address: :names, |
||||||
|
token_transfers: :token, |
||||||
|
token_transfers: :from_address, |
||||||
|
token_transfers: :to_address |
||||||
|
]) |
||||||
|
end |
||||||
|
end |
Loading…
Reference in new issue