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 ## 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 ### Features
- [#2283](https://github.com/poanetwork/blockscout/pull/2283) - Add transactions cache
- [#2182](https://github.com/poanetwork/blockscout/pull/2182) - add market history 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 - [#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 - [#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 - [#2266](https://github.com/poanetwork/blockscout/pull/2266) - allow excluding uncles from average block time calculation
### Fixes ### 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 - [#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 - [#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 - [#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 - [#2173](https://github.com/poanetwork/blockscout/pull/2173) - handle correctly empty transactions
- [#2174](https://github.com/poanetwork/blockscout/pull/2174) - fix reward channel joining - [#2174](https://github.com/poanetwork/blockscout/pull/2174) - fix reward channel joining
- [#2186](https://github.com/poanetwork/blockscout/pull/2186) - fix net version test - [#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 - [#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 - [#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 - [#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 - [#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 - [#2256](https://github.com/poanetwork/blockscout/pull/2256) - use the latest version of chromedriver
### Chore
## 2.0.0-beta ## 2.0.0-beta
### Features ### Features

@ -6,7 +6,7 @@ $card-vertical-padding: 30px;
$card-background-1: $primary !default; $card-background-1: $primary !default;
$card-background-1-text-color: #fff !default; $card-background-1-text-color: #fff !default;
$card-tab-icon-color: #20b760 !default; $card-tab-icon-color: #20b760 !default;
$card-tab-icon-color-active: #20b760 !default; $card-tab-icon-color-active: #fff !default;
.card { .card {
background-color: $card-background-color; background-color: $card-background-color;
@ -14,6 +14,21 @@ $card-tab-icon-color-active: #20b760 !default;
border: none; border: none;
box-shadow: 0 0 30px 0 rgba(202, 199, 226, 0.5); box-shadow: 0 0 30px 0 rgba(202, 199, 226, 0.5);
margin-bottom: $common-container-margin; 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 { .card-background-1 {
@ -42,6 +57,12 @@ $card-tab-icon-color-active: #20b760 !default;
line-height: 1.2rem; line-height: 1.2rem;
margin-bottom: 2rem; margin-bottom: 2rem;
&.lg-card-title {
@media (max-width: 374px) {
font-size: 13px;
}
}
&.margin-bottom-md { &.margin-bottom-md {
margin-bottom: 25px; margin-bottom: 25px;
} }

@ -80,6 +80,12 @@ $dashboard-banner-chart-axis-font-color: $dashboard-stats-item-value-color !defa
flex-direction: row; flex-direction: row;
} }
@media (max-width: 599px) {
padding-top: 0;
padding-bottom: 0;
flex-direction: column;
}
&::before { &::before {
border-radius: 2px; border-radius: 2px;
content: ""; content: "";
@ -107,6 +113,11 @@ $dashboard-banner-chart-axis-font-color: $dashboard-stats-item-value-color !defa
line-height: 1.2; line-height: 1.2;
margin: 0 0 5px; margin: 0 0 5px;
@media (max-width: 374px) {
position: relative;
top: -2px;
}
@include media-breakpoint-down(md) { @include media-breakpoint-down(md) {
margin: 0 5px 0 0; margin: 0 5px 0 0;
} }
@ -135,12 +146,16 @@ $dashboard-banner-chart-axis-font-color: $dashboard-stats-item-value-color !defa
width: 750px; width: 750px;
position: relative; position: relative;
@include media-breakpoint-down(lg) {
margin-top: 15px;
width: 550px;
}
@include media-breakpoint-down(md) { @include media-breakpoint-down(md) {
border-top-right-radius: 10px; border-top-right-radius: 10px;
height: auto; height: auto;
justify-content: flex-start; justify-content: flex-start;
margin-left: 0; margin-left: 0;
margin-top: 15px;
max-width: 100%; max-width: 100%;
padding: 20px 0 20px 20px; padding: 20px 0 20px 20px;
width: 250px; width: 250px;
@ -175,6 +190,11 @@ $dashboard-banner-chart-axis-font-color: $dashboard-stats-item-value-color !defa
display: grid; display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr; 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) { @include media-breakpoint-down(md) {
grid-template-columns: 1fr; grid-template-columns: 1fr;
row-gap: 20px; 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); @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%; 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 { .stakes-table {
min-width: fit-content; min-width: fit-content;
width: 100%; width: 100%;

@ -272,6 +272,7 @@ $tile-body-a-color: #5959d8 !default;
} }
&.tile-type-block { &.tile-type-block {
max-width: 100%;
.tile-title { .tile-title {
font-weight: 700; font-weight: 700;
line-height: 1.2; 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]) chart = createMarketHistoryChart($el[0])
}, },
render ($el, state, oldState) { 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) 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: %{ necessity_by_association: %{
:block => :required, :block => :required,
[created_contract_address: :names] => :optional, [created_contract_address: :names] => :optional,
[from_address: :names] => :required, [from_address: :names] => :optional,
[to_address: :names] => :optional [to_address: :names] => :optional
}, },
paging_options: %PagingOptions{page_size: 5} paging_options: %PagingOptions{page_size: 5}

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

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

@ -3,11 +3,48 @@
<div class="card-body" data-async-load data-async-listing="<%= @current_path %>"> <div class="card-body" data-async-load data-async-listing="<%= @current_path %>">
<h1 class="card-title"><%= gettext "Addresses" %></h1> <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 %> <%= 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> <div class="addresses-table-container">
<div class="stakes-table-container">
<%= 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 %> <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>
</div> </div>
</section> </section>

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

@ -66,10 +66,10 @@
</div> </div>
<div data-items></div> <div data-items></div>
<div class="transaction-bottom-panel"> <div class="transaction-bottom-panel">
<div class="download-all-transactions"> <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"> <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"/> <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> </svg>

@ -16,17 +16,17 @@
<%= link(@block, to: block_path(BlockScoutWeb.Endpoint, :show, @block.number)) %> <%= link(@block, to: block_path(BlockScoutWeb.Endpoint, :show, @block.number)) %>
<% end %> <% end %>
</h3> </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 --> <!-- # 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 %> <%= if @block.size do %>
<!-- Block Size --> <!-- 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 %> <% end %>
<!-- Block Age --> <!-- 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> </div>
<hr> <hr>

@ -105,7 +105,7 @@
<div class="card card-chain-transactions"> <div class="card card-chain-transactions">
<div class="card-body"> <div class="card-body">
<%= link(gettext("View All Transactions"), to: transaction_path(BlockScoutWeb.Endpoint, :index), class: "btn-line float-right") %> <%= 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="channel-batching-message" style="display: none;">
<div data-selector="reload-button" class="alert alert-info"> <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> <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 %> <% col_size = if Enum.empty?(other_explorers), do: 3, else: 2 %>
<div class="row"> <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> <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"> <div class="footer-social-icons">
<a href="https://github.com/poanetwork/blockscout" rel="noreferrer" target="_blank" class="footer-social-icon" title='<%= gettext("Github") %>'> <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> </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> <h3>BlockScout</h3>
<ul> <ul>
<li><a href="<%= issue_link(@conn) %>" rel="noreferrer" class="footer-link" target="_blank"><%= gettext("Submit an Issue") %></a></li> <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()) %> <% main_nets = main_nets(other_networks()) %>
<%= unless Enum.empty?(main_nets) do %> <%= 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> <h3><%= gettext("Main Networks") %></h3>
<ul> <ul>
<%= for %{title: title, url: url} <- main_nets do %> <%= for %{title: title, url: url} <- main_nets do %>
@ -53,7 +53,7 @@
<% test_nets = test_nets(other_networks()) %> <% test_nets = test_nets(other_networks()) %>
<%= unless Enum.empty?(test_nets) do %> <%= 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> <h3><%= gettext("Test Networks") %></h3>
<ul> <ul>
<%= for %{title: title, url: url} <- test_nets do %> <%= for %{title: title, url: url} <- test_nets do %>
@ -64,7 +64,7 @@
<% end %> <% end %>
<%= unless Enum.empty?(other_explorers) do %> <%= 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> <h3><%= gettext("Other Explorers") %></h3>
<ul> <ul>
<%= for {name, url} <- other_explorers do %> <%= for {name, url} <- other_explorers do %>

@ -51,6 +51,10 @@ defmodule BlockScoutWeb.API.RPC.AddressView do
RPCView.render("show.json", data: data) RPCView.render("show.json", data: data)
end 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 def render("eth_get_balance_error.json", %{error: message}) do
EthRPCView.render("error.json", %{error: message, id: 0}) EthRPCView.render("error.json", %{error: message, id: 0})
end end

@ -877,7 +877,7 @@ msgid "Transactions"
msgstr "" msgstr ""
#, elixir-format #, 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" msgid "Transactions sent"
msgstr "" msgstr ""
@ -923,7 +923,7 @@ msgid "Validated Transactions"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tile.html.eex:23 #: lib/block_scout_web/templates/address/_tile.html.eex:35
msgid "Validations" msgid "Validations"
msgstr "" 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()}) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, {:shared, self()})
end 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()} {:ok, conn: Phoenix.ConnTest.build_conn()}
end end
end end

@ -27,6 +27,9 @@ defmodule BlockScoutWeb.FeatureCase do
Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, {:shared, self()}) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, {:shared, self()})
end 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()) metadata = Phoenix.Ecto.SQL.Sandbox.metadata_for(Explorer.Repo, self())
{:ok, session} = Wallaby.start_session(metadata: metadata) {:ok, session} = Wallaby.start_session(metadata: metadata)
session = Wallaby.Browser.resize_window(session, 1200, 800) session = Wallaby.Browser.resize_window(session, 1200, 800)

@ -358,7 +358,9 @@ defmodule EthereumJSONRPC do
String.to_integer(hexadecimal_digits, 16) String.to_integer(hexadecimal_digits, 16)
end 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 case Integer.parse(string) do
{integer, ""} -> integer {integer, ""} -> integer
_ -> :error _ -> :error

@ -7,6 +7,8 @@ defmodule EthereumJSONRPC.HTTP do
require Logger require Logger
import EthereumJSONRPC, only: [quantity_to_integer: 1]
@behaviour Transport @behaviour Transport
@doc """ @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 # 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. # validated, so we can indicate that with switch to atom keys.
defp standardize_response(%{"jsonrpc" => "2.0" = jsonrpc, "id" => id} = unstandardized) do 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} standardized = %{jsonrpc: jsonrpc, id: id}
case unstandardized do case unstandardized do
%{"result" => _, "error" => _} -> %{"result" => _, "error" => _} ->
raise ArgumentError, 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} -> %{"result" => result} ->
Map.put(standardized, :result, result) Map.put(standardized, :result, result)

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

@ -322,6 +322,10 @@ defmodule EthereumJSONRPC.Transaction do
when key in ~w(blockHash condition creates from hash input jsonrpc publicKey raw to txType), when key in ~w(blockHash condition creates from hash input jsonrpc publicKey raw to txType),
do: {key, value} 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 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)} {key, quantity_to_integer(quantity)}
end end

@ -14,7 +14,8 @@ config :explorer,
System.get_env("ALLOWED_EVM_VERSIONS") || System.get_env("ALLOWED_EVM_VERSIONS") ||
"homestead,tangerineWhistle,spuriousDragon,byzantium,constantinople,petersburg", "homestead,tangerineWhistle,spuriousDragon,byzantium,constantinople,petersburg",
include_uncles_in_average_block_time: 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 config :explorer, Explorer.Counters.AverageBlockTime, enabled: true
@ -84,8 +85,8 @@ case System.get_env("SUPPLY_MODULE") do
:ok :ok
end end
if System.get_env("SOURCE_MODULE") == "TransactionAndLog" do if System.get_env("SOURCE_MODULE") == "TokenBridge" do
config :explorer, Explorer.ExchangeRates.Source, source: Explorer.ExchangeRates.Source.TransactionAndLog config :explorer, Explorer.ExchangeRates.Source, source: Explorer.ExchangeRates.Source.TokenBridge
end end
config :explorer, config :explorer,

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

@ -46,6 +46,7 @@ defmodule Explorer.Chain do
TokenTransfer, TokenTransfer,
Transaction, Transaction,
TransactionCountCache, TransactionCountCache,
TransactionsCache,
Wei Wei
} }
@ -1768,6 +1769,31 @@ defmodule Explorer.Chain do
Repo.one!(query) Repo.one!(query)
end 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 """ @doc """
Calculates the ranges of missing consensus blocks in `range`. 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()] @spec recent_collated_transactions([paging_options | necessity_by_association_option]) :: [Transaction.t()]
def recent_collated_transactions(options \\ []) when is_list(options) do def recent_collated_transactions(options \\ []) when is_list(options) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
options if is_nil(paging_options.key) do
|> Keyword.get(:paging_options, @default_paging_options) 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() |> fetch_transactions()
|> where([transaction], not is_nil(transaction.block_number) and not is_nil(transaction.index)) |> where([transaction], not is_nil(transaction.block_number) and not is_nil(transaction.index))
|> join_associations(necessity_by_association) |> join_associations(necessity_by_association)

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

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

@ -1,78 +0,0 @@
defmodule Explorer.Chain.Supply.TransactionAndLog do
@moduledoc """
Defines the supply API for calculating the supply for smaller chains with
specific mint and burn events
"""
use Explorer.Chain.Supply
alias Explorer.Chain.{InternalTransaction, Log, Wei}
alias Explorer.{Chain, Repo}
{:ok, base_wei} = Wei.cast(0)
@base_wei base_wei
{:ok, burn_address} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000")
@burn_address burn_address
@bridge_edge "0x3c798bbcf33115b42c728b8504cff11dd58736e9fa789f1cda2738db7d696b2a"
import Ecto.Query, only: [from: 2]
def circulating, do: total(Timex.now())
def total, do: total(Timex.now())
@doc false
@spec total(DateTime.t()) :: %Decimal{sign: 1}
def total(on_date) do
on_date
|> minted_value
|> Wei.sub(burned_value(on_date))
|> Wei.to(:ether)
end
def supply_for_days(days_count) when is_integer(days_count) and days_count > 0 do
past_days = -(days_count - 1)
result =
for i <- past_days..0, into: %{} do
datetime = Timex.shift(Timex.now(), days: i)
{DateTime.to_date(datetime), total(datetime)}
end
{:ok, result}
end
defp minted_value(on_date) do
query =
from(
l in Log,
join: t in assoc(l, :transaction),
join: b in assoc(t, :block),
where: b.timestamp <= ^on_date and l.first_topic == @bridge_edge,
select: fragment("concat('0x', encode(?, 'hex'))", l.data)
)
query
|> Repo.all()
|> Enum.reduce(@base_wei, fn data, acc ->
{:ok, wei_value} = Wei.cast(data)
Wei.sum(wei_value, acc)
end)
end
defp burned_value(on_date) do
query =
from(
it in InternalTransaction,
join: t in assoc(it, :transaction),
join: b in assoc(t, :block),
where: b.timestamp <= ^on_date and it.to_address_hash == ^@burn_address,
select: it.value
)
query
|> Repo.all()
|> Enum.reduce(@base_wei, fn data, acc -> Wei.sum(data, acc) end)
end
end

@ -0,0 +1,143 @@
defmodule Explorer.Chain.TransactionsCache do
@moduledoc """
Caches the latest imported transactions
"""
alias Explorer.Chain.Transaction
alias Explorer.Repo
@transactions_ids_key "transactions_ids"
@cache_name :transactions
@max_size 51
@preloads [
:block,
created_contract_address: :names,
from_address: :names,
to_address: :names,
token_transfers: :token,
token_transfers: :from_address,
token_transfers: :to_address
]
@spec cache_name :: atom()
def cache_name, do: @cache_name
@doc """
Fetches a transaction from its id ({block_number, index}), returns nil if not found
"""
@spec get({non_neg_integer(), non_neg_integer()}) :: Transaction.t() | nil
def get(id), do: ConCache.get(@cache_name, id)
@doc """
Return the current number of transactions stored
"""
@spec size :: non_neg_integer()
def size, do: Enum.count(transactions_ids())
@doc """
Checks if there are enough transactions stored
"""
@spec enough?(non_neg_integer()) :: boolean()
def enough?(amount) do
amount <= size()
end
@doc """
Checks if the number of transactions stored is already the max allowed
"""
@spec full? :: boolean()
def full? do
@max_size <= size()
end
@doc "Returns the list ids of the transactions currently stored"
@spec transactions_ids :: [{non_neg_integer(), non_neg_integer()}]
def transactions_ids do
ConCache.get(@cache_name, @transactions_ids_key) || []
end
@doc "Returns all the stored transactions"
@spec all :: [Transaction.t()]
def all, do: Enum.map(transactions_ids(), &get(&1))
@doc "Returns the `n` most recent transactions stored"
@spec take(integer()) :: [Transaction.t()]
def take(amount) do
transactions_ids()
|> Enum.take(amount)
|> Enum.map(&get(&1))
end
@doc """
Returns the `n` most recent transactions, unless there are not as many stored,
in which case returns `nil`
"""
@spec take_enough(integer()) :: [Transaction.t()] | nil
def take_enough(amount) do
if enough?(amount), do: take(amount)
end
@doc """
Adds a transaction (or a list of transactions).
If the cache is already full, the transaction will be only stored if it can take
the place of a less recent one.
NOTE: each transaction is inserted atomically
"""
@spec update([Transaction.t()] | Transaction.t() | nil) :: :ok
def update(transactions) when is_nil(transactions), do: :ok
def update(transactions) when is_list(transactions) do
Enum.map(transactions, &update(&1))
end
def update(transaction) do
ConCache.isolated(@cache_name, @transactions_ids_key, fn ->
transaction_id = {transaction.block_number, transaction.index}
ids = transactions_ids()
if full?() do
{init, [min]} = Enum.split(ids, -1)
cond do
transaction_id < min ->
:ok
transaction_id > min ->
insert_transaction(transaction_id, transaction, init)
ConCache.delete(@cache_name, min)
transaction_id == min ->
put_transaction(transaction_id, transaction)
end
else
insert_transaction(transaction_id, transaction, ids)
end
end)
end
defp insert_transaction(transaction_id, transaction, ids) do
put_transaction(transaction_id, transaction)
ConCache.put(@cache_name, @transactions_ids_key, insert_sorted(transaction_id, ids))
end
defp put_transaction(transaction_id, transaction) do
full_transaction = Repo.preload(transaction, @preloads)
ConCache.put(@cache_name, transaction_id, full_transaction)
end
defp insert_sorted(id, ids) do
case ids do
[] ->
[id]
[head | tail] ->
cond do
head > id -> [head | insert_sorted(id, tail)]
head < id -> [id | ids]
head == id -> ids
end
end
end
end

@ -1,6 +1,6 @@
defmodule Explorer.ExchangeRates.Source.TransactionAndLog do defmodule Explorer.ExchangeRates.Source.TokenBridge do
@moduledoc """ @moduledoc """
Adapter for calculating the market cap and total supply from logs and transactions Adapter for calculating the market cap and total supply from token bridge
while still getting other info like price in dollars and bitcoin from a secondary source while still getting other info like price in dollars and bitcoin from a secondary source
""" """

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

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

@ -42,6 +42,8 @@ defmodule Explorer.DataCase do
Explorer.Chain.BlockNumberCache.setup() Explorer.Chain.BlockNumberCache.setup()
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.BlocksCache.cache_name()}) Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.BlocksCache.cache_name()})
Supervisor.restart_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 :ok
end end

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

@ -19,7 +19,7 @@ config :indexer,
variant: EthereumJSONRPC.RSK variant: EthereumJSONRPC.RSK
], ],
subscribe_named_arguments: [ subscribe_named_arguments: [
transport: EthereumJSONRPC.WebSocket, transport: System.get_env("ETHEREUM_JSONRPC_WS_URL") && EthereumJSONRPC.WebSocket,
transport_options: [ transport_options: [
web_socket: EthereumJSONRPC.WebSocket.WebSocketClient, web_socket: EthereumJSONRPC.WebSocket.WebSocketClient,
url: System.get_env("ETHEREUM_JSONRPC_WS_URL") || "ws://localhost:8546" 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 EthereumJSONRPC.{Blocks, FetchedBeneficiaries}
alias Explorer.Chain 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.Block.Fetcher.Receipts
alias Indexer.Fetcher.{ alias Indexer.Fetcher.{
@ -173,6 +173,7 @@ defmodule Indexer.Block.Fetcher do
) do ) do
result = {:ok, %{inserted: inserted, errors: blocks_errors}} result = {:ok, %{inserted: inserted, errors: blocks_errors}}
update_block_cache(inserted[:blocks]) update_block_cache(inserted[:blocks])
update_transactions_cache(inserted[:transactions])
result result
else else
{step, {:error, reason}} -> {:error, {step, reason}} {step, {:error, reason}} -> {:error, {step, reason}}
@ -189,6 +190,10 @@ defmodule Indexer.Block.Fetcher do
BlocksCache.update_blocks(blocks) BlocksCache.update_blocks(blocks)
end end
defp update_transactions_cache(transactions) do
TransactionsCache.update(transactions)
end
def import( def import(
%__MODULE__{broadcast: broadcast, callback_module: callback_module} = state, %__MODULE__{broadcast: broadcast, callback_module: callback_module} = state,
options 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 | | `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 | | `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 | | `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 | | `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 | | `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 | | `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 | | `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+ | | `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+ | | `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+ | | `BLOCK_TRANSFORMER` | | Transformer for blocks: base or clique. | base | v1.3.4+ |
| `GRAPHIQL _TRANSACTION` | | Default transaction in query to GraphiQL. | (empty) | 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+ | | `FIRST_BLOCK` | | The block number, where indexing begins from. | 0 | v1.3.8+ |

Loading…
Cancel
Save