commit
7e64798712
@ -0,0 +1,34 @@ |
||||
<section class="container"> |
||||
<div class="card"> |
||||
<div class="card-body"> |
||||
<h1 class="card-title margin-bottom-sm"><%= gettext("ETH RPC API Documentation") %></h2> |
||||
<p class="api-text-monospace" data-endpoint-url="<%= BlockScoutWeb.Endpoint.url() %>/api/eth_rpc">[ <%= gettext "Base URL:" %> <%= @conn.host %>/api/eth_rpc ]</p> |
||||
<p class="card-subtitle margin-bottom-0"> |
||||
<%= gettext "This API is provided to support some rpc methods in the exact format specified for ethereum nodes, which can be found " %> |
||||
|
||||
<a href="https://github.com/ethereum/wiki/wiki/JSON-RPC"><%= gettext "here." %></a> |
||||
<%= gettext "This is useful to allow sending requests to blockscout without having to change anything about the request." %> |
||||
<%= gettext "However, in general, the" %> <%= link( |
||||
gettext("custom RPC"), |
||||
to: api_docs_path(@conn, :index) |
||||
) %> <%= gettext " is recommended." %> |
||||
<%= gettext "Anything not in this list is not supported. Click on the method to be taken to the documentation for that method, and check the notes section for any potential differences." %> |
||||
</p> |
||||
</div> |
||||
</div> |
||||
<div class="card"> |
||||
<div class="card-body"> |
||||
<table class="table"> |
||||
<tr> |
||||
<th>Supported Method</th> |
||||
<th>Notes</th> |
||||
</tr> |
||||
<%= for {method, info} <- Map.to_list(@documentation) do %> |
||||
<tr> |
||||
<td> <a href="https://github.com/ethereum/wiki/wiki/JSON-RPC#<%= method %>"> <%= method %> </a> </td> |
||||
<td> <%= Map.get(info, :notes, "N/A") %> </td> |
||||
</tr> |
||||
<% end %> |
||||
</table> |
||||
</div> |
||||
</section> |
@ -0,0 +1,79 @@ |
||||
defmodule Explorer.Market.MarketHistoryCache do |
||||
@moduledoc """ |
||||
Caches recent market history. |
||||
""" |
||||
|
||||
import Ecto.Query, only: [from: 2] |
||||
|
||||
alias Explorer.Market.MarketHistory |
||||
alias Explorer.Repo |
||||
|
||||
@cache_name :market_history |
||||
@last_update_key :last_update |
||||
@history_key :history |
||||
# 6 hours |
||||
@cache_period 1_000 * 60 * 60 * 6 |
||||
@recent_days 30 |
||||
|
||||
def fetch do |
||||
if cache_expired?() do |
||||
update_cache() |
||||
else |
||||
fetch_from_cache(@history_key) |
||||
end |
||||
end |
||||
|
||||
def cache_name, do: @cache_name |
||||
|
||||
def data_key, do: @history_key |
||||
|
||||
def updated_at_key, do: @last_update_key |
||||
|
||||
def recent_days_count, do: @recent_days |
||||
|
||||
defp cache_expired? do |
||||
updated_at = fetch_from_cache(@last_update_key) |
||||
|
||||
cond do |
||||
is_nil(updated_at) -> true |
||||
current_time() - updated_at > @cache_period -> true |
||||
true -> false |
||||
end |
||||
end |
||||
|
||||
defp update_cache do |
||||
new_data = fetch_from_db() |
||||
|
||||
put_into_cache(@last_update_key, current_time()) |
||||
put_into_cache(@history_key, new_data) |
||||
|
||||
new_data |
||||
end |
||||
|
||||
defp fetch_from_db do |
||||
day_diff = @recent_days * -1 |
||||
|
||||
query = |
||||
from( |
||||
mh in MarketHistory, |
||||
where: mh.date > date_add(^Date.utc_today(), ^day_diff, "day"), |
||||
order_by: [desc: mh.date] |
||||
) |
||||
|
||||
Repo.all(query) |
||||
end |
||||
|
||||
defp fetch_from_cache(key) do |
||||
ConCache.get(@cache_name, key) |
||||
end |
||||
|
||||
defp put_into_cache(key, value) do |
||||
ConCache.put(@cache_name, key, value) |
||||
end |
||||
|
||||
defp current_time do |
||||
utc_now = DateTime.utc_now() |
||||
|
||||
DateTime.to_unix(utc_now, :millisecond) |
||||
end |
||||
end |
@ -0,0 +1,90 @@ |
||||
defmodule Explorer.Market.MarketHistoryCacheTest do |
||||
use Explorer.DataCase |
||||
|
||||
alias Explorer.Market |
||||
alias Explorer.Market.MarketHistoryCache |
||||
|
||||
setup do |
||||
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, MarketHistoryCache.cache_name()}) |
||||
Supervisor.restart_child(Explorer.Supervisor, {ConCache, MarketHistoryCache.cache_name()}) |
||||
|
||||
on_exit(fn -> |
||||
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.BlocksCache.cache_name()}) |
||||
Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.BlocksCache.cache_name()}) |
||||
end) |
||||
|
||||
:ok |
||||
end |
||||
|
||||
describe "fetch/1" do |
||||
test "caches data on the first call" do |
||||
today = Date.utc_today() |
||||
|
||||
records = |
||||
for i <- 0..29 do |
||||
%{ |
||||
date: Timex.shift(today, days: i * -1), |
||||
closing_price: Decimal.new(1), |
||||
opening_price: Decimal.new(1) |
||||
} |
||||
end |
||||
|
||||
Market.bulk_insert_history(records) |
||||
|
||||
refute fetch_data() |
||||
|
||||
assert Enum.count(MarketHistoryCache.fetch()) == 30 |
||||
|
||||
assert fetch_data() == records |
||||
end |
||||
|
||||
test "updates cache if cache is stale" do |
||||
today = Date.utc_today() |
||||
|
||||
stale_records = |
||||
for i <- 0..29 do |
||||
%{ |
||||
date: Timex.shift(today, days: i * -1), |
||||
closing_price: Decimal.new(1), |
||||
opening_price: Decimal.new(1) |
||||
} |
||||
end |
||||
|
||||
Market.bulk_insert_history(stale_records) |
||||
|
||||
MarketHistoryCache.fetch() |
||||
|
||||
stale_updated_at = fetch_updated_at() |
||||
|
||||
assert fetch_data() == stale_records |
||||
|
||||
ConCache.put(MarketHistoryCache.cache_name(), MarketHistoryCache.updated_at_key(), 1) |
||||
|
||||
fetch_data() |
||||
|
||||
assert stale_updated_at != fetch_updated_at() |
||||
end |
||||
end |
||||
|
||||
defp fetch_updated_at do |
||||
ConCache.get(MarketHistoryCache.cache_name(), MarketHistoryCache.updated_at_key()) |
||||
end |
||||
|
||||
defp fetch_data do |
||||
MarketHistoryCache.cache_name() |
||||
|> ConCache.get(MarketHistoryCache.data_key()) |
||||
|> case do |
||||
nil -> |
||||
nil |
||||
|
||||
records -> |
||||
Enum.map(records, fn record -> |
||||
%{ |
||||
date: record.date, |
||||
closing_price: record.closing_price, |
||||
opening_price: record.opening_price |
||||
} |
||||
end) |
||||
end |
||||
end |
||||
end |
Loading…
Reference in new issue