Add generic Ordered Cache behaviour and implementation

Problem: multiple caches exist to keep a page of the most recent `n` elements.
More are to be implemented, so it would be good to abstract their implementation.

Solution: add a behaviour and a macro (providing an implementation) to create this type of caches.
Redefine the existing ones using the new macro.
pull/2497/head
pasqu4le 5 years ago
parent 6a9aa65e44
commit b52d10b22a
No known key found for this signature in database
GPG Key ID: 8F3EE01F1DC90687
  1. 1
      CHANGELOG.md
  2. 4
      apps/block_scout_web/test/block_scout_web/controllers/api/v1/health_controller_test.exs
  3. 4
      apps/block_scout_web/test/support/conn_case.ex
  4. 4
      apps/block_scout_web/test/support/feature_case.ex
  5. 4
      apps/explorer/lib/explorer/application.ex
  6. 14
      apps/explorer/lib/explorer/chain.ex
  7. 90
      apps/explorer/lib/explorer/chain/cache/blocks.ex
  8. 153
      apps/explorer/lib/explorer/chain/cache/transactions.ex
  9. 326
      apps/explorer/lib/explorer/chain/ordered_cache.ex
  10. 37
      apps/explorer/test/explorer/chain/cache/blocks_test.exs
  11. 4
      apps/explorer/test/explorer/market/market_history_cache_test.exs
  12. 8
      apps/explorer/test/explorer/market/market_test.exs
  13. 8
      apps/explorer/test/support/data_case.ex
  14. 2
      apps/indexer/lib/indexer/block/fetcher.ex

@ -1,6 +1,7 @@
## Current
### Features
- [#2497](https://github.com/poanetwork/blockscout/pull/2497) - Add generic Ordered Cache behaviour and implementation
### Fixes
- [#2564](https://github.com/poanetwork/blockscout/pull/2564) - fix first page button for uncles and reorgs

@ -4,8 +4,8 @@ defmodule BlockScoutWeb.API.V1.HealthControllerTest do
alias Explorer.{Chain, PagingOptions}
setup do
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()})
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
:ok
end

@ -40,8 +40,8 @@ defmodule BlockScoutWeb.ConnCase do
Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, {:shared, self()})
end
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Transactions.cache_name()})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Transactions.cache_name()})
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Transactions.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Transactions.child_id())
{:ok, conn: Phoenix.ConnTest.build_conn()}
end

@ -27,8 +27,8 @@ defmodule BlockScoutWeb.FeatureCase do
Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, {:shared, self()})
end
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Transactions.cache_name()})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Transactions.cache_name()})
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Transactions.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Transactions.child_id())
metadata = Phoenix.Ecto.SQL.Sandbox.metadata_for(Explorer.Repo, self())
{:ok, session} = Wallaby.start_session(metadata: metadata)

@ -43,11 +43,11 @@ defmodule Explorer.Application do
{Admin.Recovery, [[], [name: Admin.Recovery]]},
{TransactionCount, [[], []]},
{BlockCount, []},
con_cache_child_spec(Blocks.cache_name()),
Blocks,
con_cache_child_spec(NetVersion.cache_name()),
con_cache_child_spec(MarketHistoryCache.cache_name()),
con_cache_child_spec(RSK.cache_name(), ttl_check_interval: :timer.minutes(1), global_ttl: :timer.minutes(30)),
con_cache_child_spec(Transactions.cache_name())
Transactions
]
children = base_children ++ configurable_children()

@ -1258,14 +1258,16 @@ defmodule Explorer.Chain do
block_type = Keyword.get(options, :block_type, "Block")
if block_type == "Block" && !paging_options.key do
if Blocks.enough_elements?(paging_options.page_size) do
Blocks.blocks(paging_options.page_size)
else
elements = fetch_blocks(block_type, paging_options, necessity_by_association)
case Blocks.take_enough(paging_options.page_size) do
nil ->
elements = fetch_blocks(block_type, paging_options, necessity_by_association)
Blocks.update(elements)
Blocks.rewrite_cache(elements)
elements
elements
blocks ->
blocks
end
else
fetch_blocks(block_type, paging_options, necessity_by_association)

@ -3,87 +3,19 @@ defmodule Explorer.Chain.Cache.Blocks do
Caches the last imported blocks
"""
alias Explorer.Repo
alias Explorer.Chain.Block
@block_numbers_key "block_numbers"
@cache_name :blocks
@number_of_elements 60
use Explorer.Chain.OrderedCache,
name: :blocks,
max_size: 60,
ids_list_key: "block_numbers",
preload: :transactions,
preload: [miner: :names],
preload: :rewards
def update(block) do
numbers = block_numbers()
@type element :: Block.t()
max_number = if numbers == [], do: -1, else: Enum.max(numbers)
min_number = if numbers == [], do: -1, else: Enum.min(numbers)
@type id :: non_neg_integer()
in_range? = block.number > min_number && Enum.all?(numbers, fn number -> number != block.number end)
not_too_far_away? = block.number > max_number - @number_of_elements - 1
if (block.number > max_number || Enum.count(numbers) == 1 || in_range?) && not_too_far_away? do
if Enum.count(numbers) >= @number_of_elements do
remove_block(numbers)
put_block(block, List.delete(numbers, Enum.min(numbers)))
else
put_block(block, numbers)
end
end
end
def rewrite_cache(elements) do
numbers = block_numbers()
ConCache.delete(@cache_name, @block_numbers_key)
numbers
|> Enum.each(fn number ->
ConCache.delete(@cache_name, number)
end)
elements
|> Enum.reduce([], fn element, acc ->
put_block(element, acc)
[element.number | acc]
end)
end
def enough_elements?(number) do
ConCache.size(@cache_name) > number
end
def update_blocks(blocks) do
Enum.each(blocks, fn block ->
update(block)
end)
end
def blocks(number \\ nil) do
numbers = block_numbers()
number = if is_nil(number), do: Enum.count(numbers), else: number
numbers
|> Enum.sort()
|> Enum.reverse()
|> Enum.slice(0, number)
|> Enum.map(fn number ->
ConCache.get(@cache_name, number)
end)
end
def cache_name, do: @cache_name
def block_numbers do
ConCache.get(@cache_name, @block_numbers_key) || []
end
defp remove_block(numbers) do
min_number = Enum.min(numbers)
ConCache.delete(@cache_name, min_number)
end
defp put_block(block, numbers) do
block_with_preloads = Repo.preload(block, [:transactions, [miner: :names], :rewards])
ConCache.put(@cache_name, block.number, block_with_preloads)
ConCache.put(@cache_name, @block_numbers_key, [block.number | numbers])
end
def element_to_id(%Block{number: number}), do: number
end

@ -4,140 +4,25 @@ defmodule Explorer.Chain.Cache.Transactions do
"""
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
use Explorer.Chain.OrderedCache,
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
]
@type element :: Transaction.t()
@type id :: {non_neg_integer(), non_neg_integer()}
def element_to_id(%Transaction{block_number: block_number, index: index}) do
{block_number, index}
end
end

@ -0,0 +1,326 @@
defmodule Explorer.Chain.OrderedCache do
@moduledoc """
Behaviour for a cache of ordered elements.
A macro based on `ConCache` is provided as well, at its minimum it can be used as;
```
use Explorer.Chain.OrderedCache, name
```
where `name is an `t:atom/0` identifying the cache.
All default values can be modified by overriding their respective function or
by setting an option. For example (showing all of them):
```
use Explorer.Chain.OrderedCache,
name: :name, # need to be set
max_size: 51, # defaults to 100
ids_list_key: :ids_key, # defaults to `name`
preloads: [] # defaults to []
```
Note: `preloads` can also be set singularly with the option `preload`, e.g.:
```
use Explorer.Chain.OrderedCache,
name: :cache
preload: :block
preload: :address
preload: [transaction: :hash]
```
Additionally all of the options accepted by `ConCache.start_link/1` can be
provided as well. By default only `ttl_check_interval:` is set (to `false`).
It's also possible, and advised, to override the implementation of the `c:prevails?/2`
and `c:element_to_id/1` callbacks.
For typechecking purposes it's also recommended to override the `t:element/0`
and `t:id/0` type definitions.
"""
@type element :: struct()
@type id :: term()
@doc """
An atom that identifies this cache
"""
@callback cache_name :: atom()
@doc """
The key used to store the (ordered) list of elements.
Because this list is stored in the cache itself, one needs to make sure it is
cannot be equal to any element id.
"""
@callback ids_list_key :: term()
@doc """
The size that this cache cannot exceed.
"""
@callback max_size :: non_neg_integer()
@doc """
Fields of the stored elements that need to be preloaded.
For entities that are not stored in `Explorer.Repo` this should be empty.
"""
@callback preloads :: [term()]
@doc """
The function that orders the elements and decides the ones that are stored.
`prevails?(id_a, id_b)` should return `true` if (in case there is no space for both)
the element with `id_a` should be stored instead of the element with `id_b`,
`false` otherwise.
"""
@callback prevails?(id, id) :: boolean()
@doc """
The function that obtains an unique `t:id/0` from an `t:element/0`
"""
@callback element_to_id(element()) :: id()
@doc "Returns the list ids of the elements currently stored"
@callback ids_list :: [id]
@doc """
Fetches a element from its id, returns nil if not found
"""
@callback get(id) :: element | nil
@doc """
Return the current number of elements stored
"""
@callback size() :: non_neg_integer()
@doc """
Checks if there are enough elements stored
"""
@callback enough?(non_neg_integer()) :: boolean()
@doc """
Checks if the number of elements stored is already the max allowed
"""
@callback full? :: boolean()
@doc "Returns all the stored elements"
@callback all :: [element]
@doc "Returns the `n` most prevailing elements stored, based on `c:prevails?/2`"
@callback take(integer()) :: [element]
@doc """
Returns the `n` most prevailing elements, based on `c:prevails?/2`, unless there
are not as many stored, in which case it returns `nil`
"""
@callback take_enough(integer()) :: [element] | nil
@doc """
Adds an element, or a list of elements, to the cache.
When the cache is full, only the most prevailing elements will be stored, based
on `c:prevails?/2`.
NOTE: every update is isolated from another one.
"""
@callback update([element] | element | nil) :: :ok
defmacro __using__(name) when is_atom(name), do: do_using(name, [])
defmacro __using__(opts) when is_list(opts) do
# name is necessary
name = Keyword.fetch!(opts, :name)
do_using(name, opts)
end
# credo:disable-for-next-line /Complexity/
defp do_using(name, opts) when is_atom(name) and is_list(opts) do
ids_list_key = Keyword.get(opts, :ids_list_key, name)
max_size = Keyword.get(opts, :max_size, 100)
preloads = Keyword.get(opts, :preloads) || Keyword.get_values(opts, :preload)
concache_params =
opts
|> Keyword.drop([:ids_list_key, :max_size, :preloads, :preload])
|> Keyword.put_new(:ttl_check_interval, false)
# credo:disable-for-next-line Credo.Check.Refactor.LongQuoteBlocks
quote do
alias Explorer.Chain.OrderedCache
@behaviour OrderedCache
### Automatically set functions
@impl OrderedCache
def cache_name, do: unquote(name)
@impl OrderedCache
def ids_list_key, do: unquote(ids_list_key)
@impl OrderedCache
def max_size, do: unquote(max_size)
@impl OrderedCache
def preloads, do: unquote(preloads)
### Settable functions
@impl OrderedCache
def prevails?(id_a, id_b), do: id_a > id_b
@impl OrderedCache
def element_to_id(element), do: element
### Straightforward fetching functions
@impl OrderedCache
def ids_list, do: ConCache.get(cache_name(), ids_list_key()) || []
@impl OrderedCache
def get(id), do: ConCache.get(cache_name(), id)
@impl OrderedCache
def size, do: ids_list() |> Enum.count()
@impl OrderedCache
def enough?(amount), do: amount <= size()
@impl OrderedCache
def full?, do: max_size() <= size()
@impl OrderedCache
def all, do: Enum.map(ids_list(), &get(&1))
@impl OrderedCache
def take(amount) do
ids_list()
|> Enum.take(amount)
|> Enum.map(&get(&1))
end
@impl OrderedCache
def take_enough(amount) do
# behaves just like `if enough?(amount), do: take(amount)` but fetching
# the list only once
ids = ids_list()
if amount <= Enum.count(ids) do
ids
|> Enum.take(amount)
|> Enum.map(&get(&1))
end
end
### Updating function
@impl OrderedCache
def update(elements) when is_nil(elements), do: :ok
def update(elements) when is_list(elements) do
ConCache.update(cache_name(), ids_list_key(), fn ids ->
updated_list =
elements
|> Enum.map(&{element_to_id(&1), &1})
|> Enum.sort(&prevails?(&1, &2))
|> merge_and_update(ids || [], max_size())
{:ok, updated_list}
end)
end
def update(element), do: update([element])
defp merge_and_update(_candidates, existing, 0) do
# if there is no more space in the list remove the remaining existing
# elements and return an empty list
remove(existing)
[]
end
defp merge_and_update([], existing, size) do
# if there are no more candidates to be inserted keep as many of the
# exsisting elements and remove the rest
{remaining, to_remove} = Enum.split(existing, size)
remove(to_remove)
remaining
end
defp merge_and_update(candidates, [], size) do
# if there are still candidates and no more existing value insert as many
# candidates as possible and ignore the rest
candidates
|> Enum.take(size)
|> Enum.map(fn {element_id, element} ->
put_element(element_id, element)
element_id
end)
end
defp merge_and_update(candidates, existing, size) do
[{candidate_id, candidate} | to_check] = candidates
[head | tail] = existing
cond do
head == candidate_id ->
# if a candidate has the id of and existing element, update its value
put_element(candidate_id, candidate)
[head | merge_and_update(to_check, tail, size - 1)]
prevails?(head, candidate_id) ->
# keep the prevaling existing value and compare all candidates against the rest
[head | merge_and_update(candidates, tail, size - 1)]
true ->
# insert new prevailing candidate and compare the remaining ones with the rest
put_element(candidate_id, candidate)
[candidate_id | merge_and_update(to_check, existing, size - 1)]
end
end
defp remove(key) do
# Always performs async removal so it can wait 1/10 of a second and
# others have the time to get elements that were in the cache's list.
# Different updates cannot interfere with the removed element because
# if this was scheduled for removal it means it is too old, so following
# updates cannot insert it in the future.
Task.start(fn ->
Process.sleep(100)
if is_list(key) do
Enum.map(key, &ConCache.delete(cache_name(), &1))
else
ConCache.delete(cache_name(), key)
end
end)
end
defp put_element(element_id, element) do
full_element =
if Enum.empty?(preloads()) do
element
else
Explorer.Repo.preload(element, preloads())
end
# dirty puts are a little faster than puts with locks.
# this is not a problem because this is the only function modifying rows
# and it only gets called inside `update`, which works isolated
ConCache.dirty_put(cache_name(), element_id, full_element)
end
### Supervisor's child specification
@doc """
The child specification for a Supervisor. Note that all the `params`
provided to this function will override the ones set by using the macro
"""
def child_spec(params) do
params = Keyword.merge(unquote(concache_params), params)
Supervisor.child_spec({ConCache, params}, id: child_id())
end
def child_id, do: {ConCache, cache_name()}
defoverridable cache_name: 0,
ids_list_key: 0,
max_size: 0,
preloads: 0,
prevails?: 2,
element_to_id: 1
end
end
end

@ -16,7 +16,7 @@ defmodule Explorer.Chain.Cache.BlocksTest do
Blocks.update(block)
assert Blocks.blocks() == [block]
assert Blocks.all() == [block]
end
test "adds a new elements removing the oldest one" do
@ -30,22 +30,16 @@ defmodule Explorer.Chain.Cache.BlocksTest do
block.number
end)
assert Blocks.size() == 60
new_block = insert(:block, number: 70)
Blocks.update(new_block)
new_blocks = blocks |> List.replace_at(0, new_block.number) |> Enum.sort() |> Enum.reverse()
assert Enum.map(Blocks.blocks(), & &1.number) == new_blocks
end
test "does not add too old blocks" do
block = insert(:block, number: 100_000) |> Repo.preload([:transactions, [miner: :names], :rewards])
old_block = insert(:block, number: 1_000)
assert Blocks.full?()
Blocks.update(block)
Blocks.update(old_block)
assert Blocks.blocks() == [block]
assert Enum.map(Blocks.all(), & &1.number) == new_blocks
end
test "adds missing element" do
@ -55,30 +49,13 @@ defmodule Explorer.Chain.Cache.BlocksTest do
Blocks.update(block1)
Blocks.update(block2)
assert Enum.count(Blocks.blocks()) == 2
assert Blocks.size() == 2
block3 = insert(:block, number: 6)
Blocks.update(block3)
assert Enum.map(Blocks.blocks(), & &1.number) == [10, 6, 4]
end
end
describe "rewrite_cache/1" do
test "updates cache" do
block = insert(:block)
Blocks.update(block)
block1 = insert(:block) |> Repo.preload([:transactions, [miner: :names], :rewards])
block2 = insert(:block) |> Repo.preload([:transactions, [miner: :names], :rewards])
new_blocks = [block1, block2]
Blocks.rewrite_cache(new_blocks)
assert Blocks.blocks() == [block2, block1]
assert Enum.map(Blocks.all(), & &1.number) == [10, 6, 4]
end
end
end

@ -9,8 +9,8 @@ defmodule Explorer.Market.MarketHistoryCacheTest do
Supervisor.restart_child(Explorer.Supervisor, {ConCache, MarketHistoryCache.cache_name()})
on_exit(fn ->
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()})
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
end)
:ok

@ -6,12 +6,12 @@ defmodule Explorer.MarketTest do
alias Explorer.Repo
setup do
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()})
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
on_exit(fn ->
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()})
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
end)
:ok

@ -40,10 +40,10 @@ defmodule Explorer.DataCase do
end
Explorer.Chain.Cache.BlockNumber.setup()
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()})
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Transactions.cache_name()})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Transactions.cache_name()})
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Transactions.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Transactions.child_id())
:ok
end

@ -189,7 +189,7 @@ defmodule Indexer.Block.Fetcher do
BlockNumber.update(max_block.number)
BlockNumber.update(min_block.number)
BlocksCache.update_blocks(blocks)
BlocksCache.update(blocks)
end
defp update_transactions_cache(transactions) do

Loading…
Cancel
Save