commit
fc1dbf7c29
@ -0,0 +1,11 @@ |
|||||||
|
defmodule BlockScoutWeb.PageNotFoundControllerTest do |
||||||
|
use BlockScoutWeb.ConnCase |
||||||
|
|
||||||
|
describe "GET index/2" do |
||||||
|
test "returns 404 status", %{conn: conn} do |
||||||
|
conn = get(conn, "/wrong", %{}) |
||||||
|
|
||||||
|
assert html_response(conn, 404) |
||||||
|
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 |
@ -0,0 +1,61 @@ |
|||||||
|
defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do |
||||||
|
use Explorer.DataCase |
||||||
|
|
||||||
|
alias Ecto.Multi |
||||||
|
alias Explorer.Chain.{Data, Wei, Transaction} |
||||||
|
alias Explorer.Chain.Import.Runner.InternalTransactions |
||||||
|
|
||||||
|
describe "run/1" do |
||||||
|
test "transaction's status becomes :error when its internal_transaction has an error" do |
||||||
|
transaction = insert(:transaction) |> with_block(status: :ok) |
||||||
|
|
||||||
|
assert :ok == transaction.status |
||||||
|
|
||||||
|
index = 0 |
||||||
|
error = "Reverted" |
||||||
|
|
||||||
|
internal_transaction_changes = make_internal_transaction_changes(transaction.hash, index, error) |
||||||
|
|
||||||
|
assert {:ok, _} = run_internal_transactions([internal_transaction_changes]) |
||||||
|
|
||||||
|
assert :error == Repo.get(Transaction, transaction.hash).status |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
defp run_internal_transactions(changes_list) when is_list(changes_list) do |
||||||
|
Multi.new() |
||||||
|
|> InternalTransactions.run(changes_list, %{ |
||||||
|
timeout: :infinity, |
||||||
|
timestamps: %{inserted_at: DateTime.utc_now(), updated_at: DateTime.utc_now()} |
||||||
|
}) |
||||||
|
|> Repo.transaction() |
||||||
|
end |
||||||
|
|
||||||
|
defp make_internal_transaction_changes(transaction_hash, index, error) do |
||||||
|
%{ |
||||||
|
from_address_hash: insert(:address).hash, |
||||||
|
to_address_hash: insert(:address).hash, |
||||||
|
call_type: :call, |
||||||
|
gas: 22234, |
||||||
|
gas_used: |
||||||
|
if is_nil(error) do |
||||||
|
18920 |
||||||
|
else |
||||||
|
nil |
||||||
|
end, |
||||||
|
input: %Data{bytes: <<1>>}, |
||||||
|
output: |
||||||
|
if is_nil(error) do |
||||||
|
%Data{bytes: <<2>>} |
||||||
|
else |
||||||
|
nil |
||||||
|
end, |
||||||
|
index: index, |
||||||
|
trace_address: [], |
||||||
|
transaction_hash: transaction_hash, |
||||||
|
type: :call, |
||||||
|
value: Wei.from(Decimal.new(1), :wei), |
||||||
|
error: error |
||||||
|
} |
||||||
|
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