Merge branch 'master' into ab-async-contract-verification

pull/2264/head
Ayrat Badykov 5 years ago committed by GitHub
commit 55a5c651d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 25
      CHANGELOG.md
  2. 23
      apps/block_scout_web/assets/css/components/_card.scss
  3. 35
      apps/block_scout_web/assets/css/components/_dashboard-banner.scss
  4. 19
      apps/block_scout_web/assets/css/components/_stakes_table.scss
  5. 21
      apps/block_scout_web/assets/css/components/_tile.scss
  6. 2
      apps/block_scout_web/assets/js/pages/chain.js
  7. 49
      apps/block_scout_web/lib/block_scout_web/controllers/api/v1/health_controller.ex
  8. 2
      apps/block_scout_web/lib/block_scout_web/controllers/recent_transactions_controller.ex
  9. 2
      apps/block_scout_web/lib/block_scout_web/router.ex
  10. 84
      apps/block_scout_web/lib/block_scout_web/templates/address/_tile.html.eex
  11. 47
      apps/block_scout_web/lib/block_scout_web/templates/address/index.html.eex
  12. 2
      apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex
  13. 4
      apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex
  14. 8
      apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex
  15. 2
      apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex
  16. 10
      apps/block_scout_web/lib/block_scout_web/templates/layout/_footer.html.eex
  17. 4
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex
  18. 4
      apps/block_scout_web/priv/gettext/default.pot
  19. 50
      apps/block_scout_web/test/block_scout_web/controllers/api/v1/health_controller_test.exs
  20. 3
      apps/block_scout_web/test/support/conn_case.ex
  21. 3
      apps/block_scout_web/test/support/feature_case.ex
  22. 4
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex
  23. 9
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex
  24. 5
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex
  25. 4
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex
  26. 7
      apps/explorer/config/config.exs
  27. 26
      apps/explorer/lib/explorer/application.ex
  28. 48
      apps/explorer/lib/explorer/chain.ex
  29. 3
      apps/explorer/lib/explorer/chain/import/runner/transactions.ex
  30. 92
      apps/explorer/lib/explorer/chain/supply/rsk.ex
  31. 78
      apps/explorer/lib/explorer/chain/supply/transaction_and_log.ex
  32. 143
      apps/explorer/lib/explorer/chain/transactions_cache.ex
  33. 4
      apps/explorer/lib/explorer/exchange_rates/source/token_bridge.ex
  34. 82
      apps/explorer/test/explorer/chain/supply/rsk_test.exs
  35. 122
      apps/explorer/test/explorer/chain/supply/transaction_and_log_test.exs
  36. 95
      apps/explorer/test/explorer/chain/transactions_cache_test.exs
  37. 18
      apps/explorer/test/explorer/chain_test.exs
  38. 6
      apps/explorer/test/explorer/exchange_rates/source/token_bridge_test.exs
  39. 2
      apps/explorer/test/support/data_case.ex
  40. 2
      apps/indexer/config/dev/parity.exs
  41. 2
      apps/indexer/config/dev/rsk.exs
  42. 7
      apps/indexer/lib/indexer/block/fetcher.ex
  43. 4
      docs/env-variables.md

@ -1,5 +1,24 @@
## Current
### Features
- [#2294](https://github.com/poanetwork/blockscout/pull/2294) - add healthy block period checking endpoint
### Fixes
- [#2299](https://github.com/poanetwork/blockscout/pull/2299) - fix interpolation in error message
- [#2303](https://github.com/poanetwork/blockscout/pull/2303) - fix transaction csv download link
- [#2304](https://github.com/poanetwork/blockscout/pull/2304) - footer grid fix for md resolution
- [#2291](https://github.com/poanetwork/blockscout/pull/2291) - dashboard fix for md resolution, transactions load fix, block info row fix, addresses page issue, check mark issue
### Chore
- [#2302](https://github.com/poanetwork/blockscout/pull/2302) - fix names for xDai source
- [#2289](https://github.com/poanetwork/blockscout/pull/2289) - Optional websockets for dev environment
## 2.0.1-beta
### Features
- [#2283](https://github.com/poanetwork/blockscout/pull/2283) - Add transactions cache
- [#2182](https://github.com/poanetwork/blockscout/pull/2182) - add market history cache
- [#2109](https://github.com/poanetwork/blockscout/pull/2109) - use bigger updates instead of `Multi` transactions in BlocksTransactionsMismatch
- [#2075](https://github.com/poanetwork/blockscout/pull/2075) - add blocks cache
@ -13,6 +32,8 @@
- [#2266](https://github.com/poanetwork/blockscout/pull/2266) - allow excluding uncles from average block time calculation
### Fixes
- [#2290](https://github.com/poanetwork/blockscout/pull/2290) - Add eth_get_balance.json to AddressView's render
- [#2286](https://github.com/poanetwork/blockscout/pull/2286) - banner stats issues on sm resolutions, transactions title issue
- [#2284](https://github.com/poanetwork/blockscout/pull/2284) - add 404 status for not existing pages
- [#2244](https://github.com/poanetwork/blockscout/pull/2244) - fix internal transactions failing to be indexed because of constraint
- [#2281](https://github.com/poanetwork/blockscout/pull/2281) - typo issues, dropdown issues
@ -60,6 +81,8 @@
- [#2173](https://github.com/poanetwork/blockscout/pull/2173) - handle correctly empty transactions
- [#2174](https://github.com/poanetwork/blockscout/pull/2174) - fix reward channel joining
- [#2186](https://github.com/poanetwork/blockscout/pull/2186) - fix net version test
- [#2196](https://github.com/poanetwork/blockscout/pull/2196) - Nethermind client fixes
- [#2237](https://github.com/poanetwork/blockscout/pull/2237) - fix rsk total_supply
- [#2198](https://github.com/poanetwork/blockscout/pull/2198) - reduce transaction status and error constraint
- [#2167](https://github.com/poanetwork/blockscout/pull/2167) - feat: document eth rpc api mimicking endpoints
- [#2225](https://github.com/poanetwork/blockscout/pull/2225) - fix metadata decoding in Solidity 0.5.9 smart contract verification
@ -78,8 +101,6 @@
- [#2118](https://github.com/poanetwork/blockscout/pull/2118) - show only the last decompiled contract
- [#2256](https://github.com/poanetwork/blockscout/pull/2256) - use the latest version of chromedriver
### Chore
## 2.0.0-beta
### Features

@ -6,7 +6,7 @@ $card-vertical-padding: 30px;
$card-background-1: $primary !default;
$card-background-1-text-color: #fff !default;
$card-tab-icon-color: #20b760 !default;
$card-tab-icon-color-active: #20b760 !default;
$card-tab-icon-color-active: #fff !default;
.card {
background-color: $card-background-color;
@ -14,6 +14,21 @@ $card-tab-icon-color-active: #20b760 !default;
border: none;
box-shadow: 0 0 30px 0 rgba(202, 199, 226, 0.5);
margin-bottom: $common-container-margin;
.block-details-row {
flex-direction: row;
@include media-breakpoint-down(sm) {
flex-direction: column;
}
.block-detail-el {
& + .block-detail-el {
@include media-breakpoint-down(sm) {
margin-top: 6px;
}
}
}
}
}
.card-background-1 {
@ -42,6 +57,12 @@ $card-tab-icon-color-active: #20b760 !default;
line-height: 1.2rem;
margin-bottom: 2rem;
&.lg-card-title {
@media (max-width: 374px) {
font-size: 13px;
}
}
&.margin-bottom-md {
margin-bottom: 25px;
}

@ -80,6 +80,12 @@ $dashboard-banner-chart-axis-font-color: $dashboard-stats-item-value-color !defa
flex-direction: row;
}
@media (max-width: 599px) {
padding-top: 0;
padding-bottom: 0;
flex-direction: column;
}
&::before {
border-radius: 2px;
content: "";
@ -107,6 +113,11 @@ $dashboard-banner-chart-axis-font-color: $dashboard-stats-item-value-color !defa
line-height: 1.2;
margin: 0 0 5px;
@media (max-width: 374px) {
position: relative;
top: -2px;
}
@include media-breakpoint-down(md) {
margin: 0 5px 0 0;
}
@ -135,12 +146,16 @@ $dashboard-banner-chart-axis-font-color: $dashboard-stats-item-value-color !defa
width: 750px;
position: relative;
@include media-breakpoint-down(lg) {
margin-top: 15px;
width: 550px;
}
@include media-breakpoint-down(md) {
border-top-right-radius: 10px;
height: auto;
justify-content: flex-start;
margin-left: 0;
margin-top: 15px;
max-width: 100%;
padding: 20px 0 20px 20px;
width: 250px;
@ -175,6 +190,11 @@ $dashboard-banner-chart-axis-font-color: $dashboard-stats-item-value-color !defa
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
@include media-breakpoint-down(lg) {
grid-template-columns: 1fr 1fr;
row-gap: 20px;
}
@include media-breakpoint-down(md) {
grid-template-columns: 1fr;
row-gap: 20px;
@ -186,4 +206,17 @@ $dashboard-banner-chart-axis-font-color: $dashboard-stats-item-value-color !defa
}
@include stats-item($dashboard-stats-item-border-color, $dashboard-stats-item-label-color, $dashboard-stats-item-value-color);
.dashboard-banner-network-stats-item {
@media (max-width: 374px) {
padding-left: calc(0.6rem + 4px);
padding-right: 0.5rem;
}
}
.dashboard-banner-network-stats-value {
@media (max-width: 374px) {
font-size: 0.9rem;
}
}
}

@ -7,6 +7,25 @@ $stakes-table-cell-separation: 25px !default;
width: 100%;
}
.addresses-table-container {
margin-left: -30px;
margin-right: -30px;
.stakes-table-container {
table {
width: 100%;
th:first-child {
padding-left: 25px;
}
td:first-child {
padding-left: 25px;
}
}
}
.color-lighten {
color: #828ba0;
}
}
.stakes-table {
min-width: fit-content;
width: 100%;

@ -272,6 +272,7 @@ $tile-body-a-color: #5959d8 !default;
}
&.tile-type-block {
max-width: 100%;
.tile-title {
font-weight: 700;
line-height: 1.2;
@ -330,3 +331,23 @@ $tile-body-a-color: #5959d8 !default;
}
}
}
.card-chain-blocks {
.card-body {
.col-lg-3 {
@include media-breakpoint-down(lg) {
padding-left: 6px;
padding-right: 6px;
}
.tile-type-block {
overflow: hidden;
}
}
.row {
@include media-breakpoint-down(lg) {
margin-left: -6px;
margin-right: -6px;
}
}
}
}

@ -142,7 +142,7 @@ const elements = {
chart = createMarketHistoryChart($el[0])
},
render ($el, state, oldState) {
if (!chart || (oldState.availableSupply === state.availableSupply && oldState.marketHistoryData === state.marketHistoryData)) return
if (!chart || (oldState.availableSupply === state.availableSupply && oldState.marketHistoryData === state.marketHistoryData) || !state.availableSupply) return
chart.update(state.availableSupply, state.marketHistoryData)
}
},

@ -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

@ -12,7 +12,7 @@ defmodule BlockScoutWeb.RecentTransactionsController do
necessity_by_association: %{
:block => :required,
[created_contract_address: :names] => :optional,
[from_address: :names] => :required,
[from_address: :names] => :optional,
[to_address: :names] => :optional
},
paging_options: %PagingOptions{page_size: 5}

@ -23,6 +23,8 @@ defmodule BlockScoutWeb.Router do
get("/supply", SupplyController, :supply)
get("/health", HealthController, :health)
resources("/decompiled_smart_contract", DecompiledSmartContractController, only: [:create])
resources("/verified_smart_contracts", VerifiedSmartContractController, only: [:create])
end

@ -1,51 +1,39 @@
<div class="tile">
<div class="row">
<!-- rank -->
<div class="col-2 col-md-1 d-flex justify-content-center align-items-center">
<tr>
<td class="stakes-td">
<!-- incremented number by order in the list -->
<span>
<span class="color-lighten">
<%= @index %>
</span>
</div>
<div class="col-10 col-md-11">
<div class="row">
<div class="col-md-6 d-flex flex-column">
<%= @address |> BlockScoutWeb.AddressView.address_partial_selector(nil, nil) |> BlockScoutWeb.RenderHelpers.render_partial() %>
<!-- number of txns for this address -->
<span class="mr-4">
<span data-test="transaction_count">
<%= @tx_count %>
</span> <%= gettext "Transactions sent" %>
<% if validator?(@address) do %>
<span data-test="validation_count">
<%= @validation_count %>
</span> <%= gettext "Validations" %>
<% end %>
</span>
</div>
<!-- balance and percentage -->
<div class="col-md-6 d-flex flex-column text-md-right mt-3 mt-md-0">
<!-- address coin balance -->
<span class="tile-title" data-test="address_balance"><%= balance(@address) %></span>
<div class="d-flex flex-column flex-md-row justify-content-md-end">
<!-- USD value of the balance -->
<span
data-wei-value="<%= if @address.fetched_coin_balance, do: @address.fetched_coin_balance.value %>"
<% if !empty_exchange_rate?(@exchange_rate) do %>
data-usd-exchange-rate="<%= @exchange_rate.usd_value %>">
<% end %>
</span>
<!-- percentage of coins from total supply -->
<span class="ml-0 ml-md-2">
<%= if @total_supply do %>
(<%= balance_percentage(@address, @total_supply) %>)
<% end %>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</td>
<td class="stakes-td">
<%= @address |> BlockScoutWeb.AddressView.address_partial_selector(nil, nil) |> BlockScoutWeb.RenderHelpers.render_partial() %>
</td>
<td class="stakes-td">
<span data-test="address_balance"><%= balance(@address) %></span>
<!-- USD value of the balance -->
<span
data-wei-value="<%= if @address.fetched_coin_balance, do: @address.fetched_coin_balance.value %>"
<% if !empty_exchange_rate?(@exchange_rate) do %>
data-usd-exchange-rate="<%= @exchange_rate.usd_value %>">
<% end %>
</span>
</td>
<td class="stakes-td color-lighten">
<!-- percentage of coins from total supply -->
<%= if @total_supply do %>
(<%= balance_percentage(@address, @total_supply) %>)
<% end %>
</td>
<td class="stakes-td">
<span class="mr-4">
<span data-test="transaction_count">
<%= @tx_count %>
</span> <%= gettext "Transactions sent" %>
<% if validator?(@address) do %>
<span data-test="validation_count">
<%= @validation_count %>
</span> <%= gettext "Validations" %>
<% end %>
</span>
</td>
</tr>

@ -3,11 +3,48 @@
<div class="card-body" data-async-load data-async-listing="<%= @current_path %>">
<h1 class="card-title"><%= gettext "Addresses" %></h1>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
<div data-items data-selector="top-addresses-list"></div>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
<div class="addresses-table-container">
<div class="stakes-table-container">
<table>
<thead>
<tr>
<th class="stakes-table-th">
<div class="stakes-table-th-content">
&nbsp;
</div>
</th>
<th class="stakes-table-th">
<div class="stakes-table-th-content">
Address
</div>
</th>
<th class="stakes-table-th">
<div class="stakes-table-th-content">
Balance
</div>
</th>
<th class="stakes-table-th">
<div class="stakes-table-th-content">
Percentage
</div>
</th>
<th class="stakes-table-th">
<div class="stakes-table-th-content">
Txn Count
</div>
</th>
</tr>
</thead>
<tbody data-items data-selector="top-addresses-list">
</tbody>
</table>
</div>
</div>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
</div>
</div>
</section>

@ -4,7 +4,7 @@
<div class="card-section col-md-12 col-lg-8 pr-0-md">
<div class="card">
<div class="card-body">
<h1 class="card-title">
<h1 class="card-title lg-card-title">
<%= address_title(@address) %> <%= gettext "Details" %>
<!-- buttons -->
<span class="overview-title-buttons float-right">

@ -66,10 +66,10 @@
</div>
<div data-items></div>
<div class="transaction-bottom-panel">
<div class="download-all-transactions">
Download <a class="download-all-transactions-link" href=<%= address_transaction_path(@conn, :token_transfers_csv, %{"address_id" => to_string(@address.hash)}) %>><%= gettext("CSV") %></span>
Download <a class="download-all-transactions-link" href=<%= address_transaction_path(@conn, :transactions_csv, %{"address_id" => to_string(@address.hash)}) %>><%= gettext("CSV") %></span>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="16">
<path fill="#333333" fill-rule="evenodd" d="M13 16H1c-.999 0-1-1-1-1V1s-.004-1 1-1h6l7 7v8s-.032 1-1 1zm-1-8c0-.99-1-1-1-1H8s-1 .001-1-1V3c0-.999-1-1-1-1H2v12h10V8z"/>
</svg>

@ -16,17 +16,17 @@
<%= link(@block, to: block_path(BlockScoutWeb.Endpoint, :show, @block.number)) %>
<% end %>
</h3>
<div class="d-flex flex-row justify-content-start text-muted">
<div class="d-flex justify-content-start text-muted block-details-row">
<!-- # of Transactions -->
<span class="mr-4"> <%= gettext "%{count} Transactions", count: @block_transaction_count %> </span>
<span class="mr-4 block-detail-el"> <%= gettext "%{count} Transactions", count: @block_transaction_count %> </span>
<%= if @block.size do %>
<!-- Block Size -->
<span class="mr-4"> <%= Cldr.Unit.new(:byte, @block.size) |> cldr_unit_to_string!() %> </span>
<span class="mr-4 block-detail-el"> <%= Cldr.Unit.new(:byte, @block.size) |> cldr_unit_to_string!() %> </span>
<% end %>
<!-- Block Age -->
<span class="mr-4" data-from-now="<%= @block.timestamp %>"></span>
<span class="mr-4 block-detail-el" data-from-now="<%= @block.timestamp %>"></span>
</div>
<hr>

@ -105,7 +105,7 @@
<div class="card card-chain-transactions">
<div class="card-body">
<%= link(gettext("View All Transactions"), to: transaction_path(BlockScoutWeb.Endpoint, :index), class: "btn-line float-right") %>
<h2 class="card-title"><%= gettext "Transactions" %></h2>
<h2 class="card-title lg-card-title"><%= gettext "Transactions" %></h2>
<div data-selector="channel-batching-message" style="display: none;">
<div data-selector="reload-button" class="alert alert-info">
<a href="#" class="alert-link"><span data-selector="channel-batching-count"></span> <%= gettext "More transactions have come in" %></a>

@ -13,7 +13,7 @@
<% col_size = if Enum.empty?(other_explorers), do: 3, else: 2 %>
<div class="row">
<div class="col-md-3">
<div class="col-xs-12 col-lg-3">
<p class="footer-info-text"><%= gettext("Blockscout is a tool for inspecting and analyzing EVM based blockchains. Blockchain explorer for Ethereum Networks.") %></p>
<div class="footer-social-icons">
<a href="https://github.com/poanetwork/blockscout" rel="noreferrer" target="_blank" class="footer-social-icon" title='<%= gettext("Github") %>'>
@ -28,7 +28,7 @@
</div>
</div>
<div class="col-md-<%= col_size %> footer-list">
<div class="col-xs-12 col-md-4 col-lg-<%= col_size %> footer-list">
<h3>BlockScout</h3>
<ul>
<li><a href="<%= issue_link(@conn) %>" rel="noreferrer" class="footer-link" target="_blank"><%= gettext("Submit an Issue") %></a></li>
@ -40,7 +40,7 @@
<% main_nets = main_nets(other_networks()) %>
<%= unless Enum.empty?(main_nets) do %>
<div class="col-md-<%= col_size %> footer-list">
<div class="col-xs-12 col-md-4 col-lg-<%= col_size %> footer-list">
<h3><%= gettext("Main Networks") %></h3>
<ul>
<%= for %{title: title, url: url} <- main_nets do %>
@ -53,7 +53,7 @@
<% test_nets = test_nets(other_networks()) %>
<%= unless Enum.empty?(test_nets) do %>
<div class="col-md-<%= col_size %> footer-list">
<div class="col-xs-12 col-md-4 col-lg-<%= col_size %> footer-list">
<h3><%= gettext("Test Networks") %></h3>
<ul>
<%= for %{title: title, url: url} <- test_nets do %>
@ -64,7 +64,7 @@
<% end %>
<%= unless Enum.empty?(other_explorers) do %>
<div class="col-md-<%= col_size %> footer-list">
<div class="col-xs-12 col-md-4 col-lg-<%= col_size %> footer-list">
<h3><%= gettext("Other Explorers") %></h3>
<ul>
<%= for {name, url} <- other_explorers do %>

@ -51,6 +51,10 @@ defmodule BlockScoutWeb.API.RPC.AddressView do
RPCView.render("show.json", data: data)
end
def render("eth_get_balance.json", %{balance: balance}) do
EthRPCView.render("show.json", %{result: balance, id: 0})
end
def render("eth_get_balance_error.json", %{error: message}) do
EthRPCView.render("error.json", %{error: message, id: 0})
end

@ -877,7 +877,7 @@ msgid "Transactions"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tile.html.eex:19
#: lib/block_scout_web/templates/address/_tile.html.eex:31
msgid "Transactions sent"
msgstr ""
@ -923,7 +923,7 @@ msgid "Validated Transactions"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tile.html.eex:23
#: lib/block_scout_web/templates/address/_tile.html.eex:35
msgid "Validations"
msgstr ""

@ -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

@ -38,6 +38,9 @@ defmodule BlockScoutWeb.ConnCase do
Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, {:shared, self()})
end
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.TransactionsCache.cache_name()})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.TransactionsCache.cache_name()})
{:ok, conn: Phoenix.ConnTest.build_conn()}
end
end

@ -27,6 +27,9 @@ defmodule BlockScoutWeb.FeatureCase do
Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, {:shared, self()})
end
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.TransactionsCache.cache_name()})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.TransactionsCache.cache_name()})
metadata = Phoenix.Ecto.SQL.Sandbox.metadata_for(Explorer.Repo, self())
{:ok, session} = Wallaby.start_session(metadata: metadata)
session = Wallaby.Browser.resize_window(session, 1200, 800)

@ -358,7 +358,9 @@ defmodule EthereumJSONRPC do
String.to_integer(hexadecimal_digits, 16)
end
def quantity_to_integer(string) do
def quantity_to_integer(integer) when is_integer(integer), do: integer
def quantity_to_integer(string) when is_binary(string) do
case Integer.parse(string) do
{integer, ""} -> integer
_ -> :error

@ -7,6 +7,8 @@ defmodule EthereumJSONRPC.HTTP do
require Logger
import EthereumJSONRPC, only: [quantity_to_integer: 1]
@behaviour Transport
@doc """
@ -131,12 +133,17 @@ defmodule EthereumJSONRPC.HTTP do
# restrict response to only those fields supported by the JSON-RPC 2.0 standard, which means that level of keys is
# validated, so we can indicate that with switch to atom keys.
defp standardize_response(%{"jsonrpc" => "2.0" = jsonrpc, "id" => id} = unstandardized) do
# Nethermind return string ids
id = quantity_to_integer(id)
standardized = %{jsonrpc: jsonrpc, id: id}
case unstandardized do
%{"result" => _, "error" => _} ->
raise ArgumentError,
"result and error keys are mutually exclusive in JSONRPC 2.0 response objects, but got #{unstandardized}"
"result and error keys are mutually exclusive in JSONRPC 2.0 response objects, but got #{
inspect(unstandardized)
}"
%{"result" => result} ->
Map.put(standardized, :result, result)

@ -294,6 +294,11 @@ defmodule EthereumJSONRPC.Receipt do
:ignore
end
# Nethermind field
defp entry_to_elixir({"error", _}) do
:ignore
end
defp entry_to_elixir({key, value}) do
{:error, {:unknown_key, %{key: key, value: value}}}
end

@ -322,6 +322,10 @@ defmodule EthereumJSONRPC.Transaction do
when key in ~w(blockHash condition creates from hash input jsonrpc publicKey raw to txType),
do: {key, value}
# specific to Nethermind client
defp entry_to_elixir({"data", value}),
do: {"input", value}
defp entry_to_elixir({key, quantity}) when key in ~w(gas gasPrice nonce r s standardV v value) and quantity != nil do
{key, quantity_to_integer(quantity)}
end

@ -14,7 +14,8 @@ config :explorer,
System.get_env("ALLOWED_EVM_VERSIONS") ||
"homestead,tangerineWhistle,spuriousDragon,byzantium,constantinople,petersburg",
include_uncles_in_average_block_time:
if(System.get_env("UNCLES_IN_AVERAGE_BLOCK_TIME") == "false", do: false, else: true)
if(System.get_env("UNCLES_IN_AVERAGE_BLOCK_TIME") == "false", do: false, else: true),
healthy_blocks_period: System.get_env("HEALTHY_BLOCKS_PERIOD") || :timer.minutes(5)
config :explorer, Explorer.Counters.AverageBlockTime, enabled: true
@ -84,8 +85,8 @@ case System.get_env("SUPPLY_MODULE") do
:ok
end
if System.get_env("SOURCE_MODULE") == "TransactionAndLog" do
config :explorer, Explorer.ExchangeRates.Source, source: Explorer.ExchangeRates.Source.TransactionAndLog
if System.get_env("SOURCE_MODULE") == "TokenBridge" do
config :explorer, Explorer.ExchangeRates.Source, source: Explorer.ExchangeRates.Source.TokenBridge
end
config :explorer,

@ -6,7 +6,18 @@ defmodule Explorer.Application do
use Application
alias Explorer.Admin
alias Explorer.Chain.{BlockCountCache, BlockNumberCache, BlocksCache, NetVersionCache, TransactionCountCache}
alias Explorer.Chain.{
BlockCountCache,
BlockNumberCache,
BlocksCache,
NetVersionCache,
TransactionCountCache,
TransactionsCache
}
alias Explorer.Chain.Supply.RSK
alias Explorer.Market.MarketHistoryCache
alias Explorer.Repo.PrometheusLogger
@ -34,7 +45,9 @@ defmodule Explorer.Application do
{BlockCountCache, []},
con_cache_child_spec(BlocksCache.cache_name()),
con_cache_child_spec(NetVersionCache.cache_name()),
con_cache_child_spec(MarketHistoryCache.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(TransactionsCache.cache_name())
]
children = base_children ++ configurable_children()
@ -83,14 +96,13 @@ defmodule Explorer.Application do
]
end
defp con_cache_child_spec(name) do
defp con_cache_child_spec(name, params \\ [ttl_check_interval: false]) do
params = Keyword.put(params, :name, name)
Supervisor.child_spec(
{
ConCache,
[
name: name,
ttl_check_interval: false
]
params
},
id: {ConCache, name}
)

@ -46,6 +46,7 @@ defmodule Explorer.Chain do
TokenTransfer,
Transaction,
TransactionCountCache,
TransactionsCache,
Wei
}
@ -1768,6 +1769,31 @@ defmodule Explorer.Chain do
Repo.one!(query)
end
def last_block_status do
query =
from(block in Block,
select: {block.number, block.timestamp},
where: block.consensus == true,
order_by: [desc: block.number],
limit: 1
)
case Repo.one(query) do
nil ->
{:error, :no_blocks}
{number, timestamp} ->
now = DateTime.utc_now()
last_block_period = DateTime.diff(now, timestamp, :millisecond)
if last_block_period > Application.get_env(:explorer, :healthy_blocks_period) do
{:error, number, timestamp}
else
{:ok, number, timestamp}
end
end
end
@doc """
Calculates the ranges of missing consensus blocks in `range`.
@ -1948,9 +1974,27 @@ defmodule Explorer.Chain do
@spec recent_collated_transactions([paging_options | necessity_by_association_option]) :: [Transaction.t()]
def recent_collated_transactions(options \\ []) when is_list(options) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
options
|> Keyword.get(:paging_options, @default_paging_options)
if is_nil(paging_options.key) do
paging_options.page_size
|> TransactionsCache.take_enough()
|> case do
nil ->
transactions = fetch_recent_collated_transactions(paging_options, necessity_by_association)
TransactionsCache.update(transactions)
transactions
transactions ->
transactions
end
else
fetch_recent_collated_transactions(paging_options, necessity_by_association)
end
end
def fetch_recent_collated_transactions(paging_options, necessity_by_association) do
paging_options
|> fetch_transactions()
|> where([transaction], not is_nil(transaction.block_number) and not is_nil(transaction.index))
|> join_associations(necessity_by_association)

@ -90,8 +90,7 @@ defmodule Explorer.Chain.Import.Runner.Transactions do
conflict_target: :hash,
on_conflict: on_conflict,
for: Transaction,
returning:
~w(block_number index hash internal_transactions_indexed_at block_hash old_block_hash nonce from_address_hash created_contract_address_hash)a,
returning: true,
timeout: timeout,
timestamps: timestamps
)

@ -6,14 +6,20 @@ defmodule Explorer.Chain.Supply.RSK do
use Explorer.Chain.Supply
import Ecto.Query, only: [from: 2]
import EthereumJSONRPC, only: [integer_to_quantity: 1]
alias EthereumJSONRPC.FetchedBalances
alias Explorer.Chain.Address.CoinBalance
alias Explorer.Chain.{Block, Wei}
alias Explorer.ExchangeRates.Token
alias Explorer.{Market, Repo}
alias Explorer.Chain.{Block, BlockNumberCache, Wei}
alias Explorer.Repo
@cache_name :rsk_balance
@balance_key :balance
def market_cap(exchange_rate) do
circulating() * exchange_rate.usd_value
btc = circulating()
Decimal.mult(btc, exchange_rate.usd_value)
end
@doc "Equivalent to getting the circulating value "
@ -60,13 +66,16 @@ defmodule Explorer.Chain.Supply.RSK do
|> Timex.shift(days: i)
|> Timex.to_date()
case Map.get(by_day, date) do
nil ->
{Map.put(days, date, last), last}
cur_value =
case Map.get(by_day, date) do
nil ->
last
value ->
value.value
end
value ->
{Map.put(days, date, value.value), value.value}
end
{Map.put(days, date, calculate_value(cur_value)), cur_value}
end)
|> elem(0)
@ -74,18 +83,48 @@ defmodule Explorer.Chain.Supply.RSK do
end
def circulating do
query =
from(balance in CoinBalance,
join: block in Block,
on: block.number == balance.block_number,
where: block.consensus == true,
where: balance.address_hash == ^"0x0000000000000000000000000000000001000006",
order_by: [desc: block.timestamp],
limit: 1,
select: balance.value
)
value = ConCache.get(@cache_name, @balance_key)
if is_nil(value) do
updated_value = fetch_circulating_value()
Repo.one(query) || wei!(0)
ConCache.put(@cache_name, @balance_key, updated_value)
updated_value
else
value
end
end
def cache_name, do: @cache_name
defp fetch_circulating_value do
max_number = BlockNumberCache.max_number()
params = [
%{block_quantity: integer_to_quantity(max_number), hash_data: "0x0000000000000000000000000000000001000006"}
]
json_rpc_named_argumens = Application.get_env(:explorer, :json_rpc_named_arguments)
case EthereumJSONRPC.fetch_balances(params, json_rpc_named_argumens) do
{:ok,
%FetchedBalances{
errors: [],
params_list: [
%{
address_hash: "0x0000000000000000000000000000000001000006",
value: value
}
]
}} ->
calculate_value(value)
_ ->
Decimal.new(0)
end
rescue
_ -> Decimal.new(0)
end
defp wei!(value) do
@ -94,10 +133,15 @@ defmodule Explorer.Chain.Supply.RSK do
end
def total do
21_000_000
Decimal.new(21_000_000)
end
def exchange_rate do
Market.get_exchange_rate(Explorer.coin()) || Token.null()
defp calculate_value(val) do
sub =
val
|> Decimal.new()
|> Decimal.div(Decimal.new(1_000_000_000_000_000_000))
Decimal.sub(total(), sub)
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 """
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
"""

@ -1,32 +1,30 @@
defmodule Explorer.Chain.Supply.RSKTest do
use Explorer.DataCase
import Mox
alias Explorer.Chain.Supply.RSK
alias Explorer.Chain.Wei
alias Explorer.ExchangeRates.Token
@coin_address "0x0000000000000000000000000000000001000006"
defp wei!(value) do
{:ok, wei} = Wei.cast(value)
wei
end
@mult 1_000_000_000_000_000_000
test "total is 21_000_000" do
assert RSK.total() == 21_000_000
assert Decimal.equal?(RSK.total(), Decimal.new(21_000_000))
end
describe "circulating/0" do
test "with no balance" do
assert RSK.circulating() == wei!(0)
end
test "with a balance" do
address = insert(:address, hash: @coin_address)
insert(:block, number: 0)
describe "market_cap/1" do
@tag :no_parity
@tag :no_geth
test "calculates market_cap" do
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [%{id: id, method: "eth_getBalance"}], _options ->
{:ok, [%{id: id, result: "20999999999900000000000000"}]}
end)
insert(:fetched_balance, value: 10, address_hash: address.hash, block_number: 0)
exchange_rate = %{Token.null() | usd_value: Decimal.new(1_000_000)}
assert RSK.circulating() == wei!(10)
assert Decimal.equal?(RSK.market_cap(exchange_rate), Decimal.new(100.0000))
end
end
@ -47,9 +45,9 @@ defmodule Explorer.Chain.Supply.RSKTest do
assert RSK.supply_for_days(2) ==
{:ok,
%{
date(now, days: -2) => dec(0),
date(now, days: -1) => dec(0),
date(now) => dec(0)
date(now, days: -2) => dec(21_000_000),
date(now, days: -1) => dec(21_000_000),
date(now) => dec(21_000_000)
}}
end
@ -59,14 +57,14 @@ defmodule Explorer.Chain.Supply.RSKTest do
insert(:block, number: 0, timestamp: Timex.shift(now, days: -10))
insert(:fetched_balance, value: 10, address_hash: address.hash, block_number: 0)
insert(:fetched_balance, value: 10 * @mult, address_hash: address.hash, block_number: 0)
assert RSK.supply_for_days(2) ==
{:ok,
%{
date(now, days: -2) => dec(10),
date(now, days: -1) => dec(10),
date(now) => dec(10)
date(now, days: -2) => dec(20_999_990),
date(now, days: -1) => dec(20_999_990),
date(now) => dec(20_999_990)
}}
end
@ -77,16 +75,16 @@ defmodule Explorer.Chain.Supply.RSKTest do
insert(:block, number: 0, timestamp: Timex.shift(now, days: -10))
insert(:block, number: 1, timestamp: Timex.shift(now, days: -1))
insert(:fetched_balance, value: 10, address_hash: address.hash, block_number: 0)
insert(:fetched_balance, value: 10 * @mult, address_hash: address.hash, block_number: 0)
insert(:fetched_balance, value: 20, address_hash: address.hash, block_number: 1)
insert(:fetched_balance, value: 20 * @mult, address_hash: address.hash, block_number: 1)
assert RSK.supply_for_days(2) ==
{:ok,
%{
date(now, days: -2) => dec(10),
date(now, days: -1) => dec(20),
date(now) => dec(20)
date(now, days: -2) => dec(20_999_990),
date(now, days: -1) => dec(20_999_980),
date(now) => dec(20_999_980)
}}
end
@ -98,18 +96,18 @@ defmodule Explorer.Chain.Supply.RSKTest do
insert(:block, number: 1, timestamp: Timex.shift(now, days: -2))
insert(:block, number: 2, timestamp: Timex.shift(now, days: -1))
insert(:fetched_balance, value: 5, address_hash: address.hash, block_number: 0)
insert(:fetched_balance, value: 5 * @mult, address_hash: address.hash, block_number: 0)
insert(:fetched_balance, value: 10, address_hash: address.hash, block_number: 1)
insert(:fetched_balance, value: 10 * @mult, address_hash: address.hash, block_number: 1)
insert(:fetched_balance, value: 20, address_hash: address.hash, block_number: 2)
insert(:fetched_balance, value: 20 * @mult, address_hash: address.hash, block_number: 2)
assert RSK.supply_for_days(2) ==
{:ok,
%{
date(now, days: -2) => dec(10),
date(now, days: -1) => dec(20),
date(now) => dec(20)
date(now, days: -2) => dec(20_999_990),
date(now, days: -1) => dec(20_999_980),
date(now) => dec(20_999_980)
}}
end
@ -122,17 +120,17 @@ defmodule Explorer.Chain.Supply.RSKTest do
insert(:block, number: 2, timestamp: Timex.shift(now, days: -1))
insert(:block, number: 3, timestamp: now)
insert(:fetched_balance, value: 5, address_hash: address.hash, block_number: 0)
insert(:fetched_balance, value: 10, address_hash: address.hash, block_number: 1)
insert(:fetched_balance, value: 20, address_hash: address.hash, block_number: 2)
insert(:fetched_balance, value: 30, address_hash: address.hash, block_number: 3)
insert(:fetched_balance, value: 5 * @mult, address_hash: address.hash, block_number: 0)
insert(:fetched_balance, value: 10 * @mult, address_hash: address.hash, block_number: 1)
insert(:fetched_balance, value: 20 * @mult, address_hash: address.hash, block_number: 2)
insert(:fetched_balance, value: 30 * @mult, address_hash: address.hash, block_number: 3)
assert RSK.supply_for_days(2) ==
{:ok,
%{
date(now, days: -2) => dec(10),
date(now, days: -1) => dec(20),
date(now) => dec(30)
date(now, days: -2) => dec(20_999_990),
date(now, days: -1) => dec(20_999_980),
date(now) => dec(20_999_970)
}}
end
end

@ -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

@ -50,6 +50,24 @@ defmodule Explorer.ChainTest do
end
end
describe "last_block_status/0" do
test "return no_blocks errors if db is empty" do
assert {:error, :no_blocks} = Chain.last_block_status()
end
test "returns {:ok, last_block_period} if block is in healthy period" do
insert(:block, consensus: true)
assert {:ok, _, _} = Chain.last_block_status()
end
test "return {:ok, last_block_period} if block is not in healthy period" do
insert(:block, consensus: true, timestamp: Timex.shift(DateTime.utc_now(), hours: -50))
assert {:error, _, _} = Chain.last_block_status()
end
end
describe "address_to_logs/2" do
test "fetches logs" do
address = insert(:address)

@ -1,6 +1,6 @@
defmodule Explorer.ExchangeRates.Source.TransactionAndLogTest do
defmodule Explorer.ExchangeRates.Source.TokenBridgeTest do
use Explorer.DataCase
alias Explorer.ExchangeRates.Source.TransactionAndLog
alias Explorer.ExchangeRates.Source.TokenBridge
alias Explorer.ExchangeRates.Token
@json """
@ -27,7 +27,7 @@ defmodule Explorer.ExchangeRates.Source.TransactionAndLogTest do
describe "format_data/1" do
test "bring a list with one %Token{}" do
assert [%Token{}] = TransactionAndLog.format_data(@json)
assert [%Token{}] = TokenBridge.format_data(@json)
end
end
end

@ -42,6 +42,8 @@ defmodule Explorer.DataCase do
Explorer.Chain.BlockNumberCache.setup()
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.BlocksCache.cache_name()})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.BlocksCache.cache_name()})
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.TransactionsCache.cache_name()})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.TransactionsCache.cache_name()})
:ok
end

@ -34,7 +34,7 @@ config :indexer,
# ]
# ],
subscribe_named_arguments: [
transport: EthereumJSONRPC.WebSocket,
transport: System.get_env("ETHEREUM_JSONRPC_WS_URL") && EthereumJSONRPC.WebSocket,
transport_options: [
web_socket: EthereumJSONRPC.WebSocket.WebSocketClient,
url: System.get_env("ETHEREUM_JSONRPC_WS_URL") || "ws://localhost:8546"

@ -19,7 +19,7 @@ config :indexer,
variant: EthereumJSONRPC.RSK
],
subscribe_named_arguments: [
transport: EthereumJSONRPC.WebSocket,
transport: System.get_env("ETHEREUM_JSONRPC_WS_URL") && EthereumJSONRPC.WebSocket,
transport_options: [
web_socket: EthereumJSONRPC.WebSocket.WebSocketClient,
url: System.get_env("ETHEREUM_JSONRPC_WS_URL") || "ws://localhost:8546"

@ -11,7 +11,7 @@ defmodule Indexer.Block.Fetcher do
alias EthereumJSONRPC.{Blocks, FetchedBeneficiaries}
alias Explorer.Chain
alias Explorer.Chain.{Address, Block, BlockNumberCache, BlocksCache, Hash, Import, Transaction}
alias Explorer.Chain.{Address, Block, BlockNumberCache, BlocksCache, Hash, Import, Transaction, TransactionsCache}
alias Indexer.Block.Fetcher.Receipts
alias Indexer.Fetcher.{
@ -173,6 +173,7 @@ defmodule Indexer.Block.Fetcher do
) do
result = {:ok, %{inserted: inserted, errors: blocks_errors}}
update_block_cache(inserted[:blocks])
update_transactions_cache(inserted[:transactions])
result
else
{step, {:error, reason}} -> {:error, {step, reason}}
@ -189,6 +190,10 @@ defmodule Indexer.Block.Fetcher do
BlocksCache.update_blocks(blocks)
end
defp update_transactions_cache(transactions) do
TransactionsCache.update(transactions)
end
def import(
%__MODULE__{broadcast: broadcast, callback_module: callback_module} = state,
options

@ -21,7 +21,7 @@ Below is a table outlining the environment variables utilized by BlockScout.
| `METADATA_CONTRACT` | | This environment variable is specifically used by POA Network to obtain Validators information to display in the UI. | (empty) | all |
| `VALIDATORS_CONTRACT` | | This environment variable is specifically used by POA Network to obtain the Emission Fund contract. | (empty) | all |
| `SUPPLY_MODULE` | | This environment variable is used by the xDai Chain in order to tell the application how to calculate the total supply of the chain. | false | all |
| `SOURCE_MODULE` | | This environment variable is used to calculate the total supply and is specifically used by the xDai Chain. | false | all |
| `SOURCE_MODULE` | | This environment variable is used to calculate the exchange rate and is specifically used by the xDai Chain. | false | all |
| `DATABASE_URL` | | Production environment variable to define the Database endpoint. | (empty) | all |
| `POOL_SIZE` | | Production environment variable to define the number of database connections allowed. | 20 | all |
| `ECTO_USE_SSL`| | Production environment variable to use SSL on Ecto queries. | true | all |
@ -33,7 +33,7 @@ Below is a table outlining the environment variables utilized by BlockScout.
| `HEART_COMMAND` | | Production environment variable to restart the application in the event of a crash. | systemctl restart explorer.service | all |
| `BLOCKSCOUT_VERSION` | | Added to the footer to signify the current BlockScout version. | (empty) | v1.3.4+ |
| `RELEASE_LINK` | | The link to Blockscout release notes in the footer. | https://github.com/poanetwork/ <br /> <u>blockscout/releases/</u> <br /> <u>tag/${BLOCKSCOUT_VERSION}</u> | v1.3.5+ |
| `ELIXIR_VERSION` | | Elixir version to install on the node before Blockscout deploy. | (empty) | all |
| `ELIXIR_VERSION` | | Elixir version to install on the node before Blockscout deploy. | (empty) | all |
| `BLOCK_TRANSFORMER` | | Transformer for blocks: base or clique. | base | v1.3.4+ |
| `GRAPHIQL _TRANSACTION` | | Default transaction in query to GraphiQL. | (empty) | v1.3.4+ |
| `FIRST_BLOCK` | | The block number, where indexing begins from. | 0 | v1.3.8+ |

Loading…
Cancel
Save