Merge branch 'master' into remove-duplicate-token-transfers

pull/2062/head
Victor Baranov 6 years ago committed by GitHub
commit bd98c90048
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 28
      CHANGELOG.md
  2. 2
      apps/block_scout_web/assets/css/app.scss
  3. 64
      apps/block_scout_web/assets/css/components/_log-search.scss
  4. 1
      apps/block_scout_web/assets/js/app.js
  5. 6
      apps/block_scout_web/assets/js/lib/async_listing_load.js
  6. 75
      apps/block_scout_web/assets/js/pages/address/logs.js
  7. 44
      apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex
  8. 13
      apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex
  9. 2
      apps/block_scout_web/lib/block_scout_web/router.ex
  10. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex
  11. 12
      apps/block_scout_web/lib/block_scout_web/templates/address_logs/index.html.eex
  12. 12
      apps/block_scout_web/priv/gettext/default.pot
  13. 80
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  14. 43
      apps/block_scout_web/test/block_scout_web/controllers/address_internal_transaction_controller_test.exs
  15. 10
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/logs_controller_test.exs
  16. 9
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex
  17. 19
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex
  18. 63
      apps/explorer/lib/explorer/chain.ex
  19. 93
      apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex
  20. 91
      apps/explorer/lib/explorer/chain/import/runner/staking_pools_delegators.ex
  21. 3
      apps/explorer/lib/explorer/chain/import/stage/address_referencing.ex
  22. 7
      apps/explorer/lib/explorer/chain/internal_transaction.ex
  23. 97
      apps/explorer/lib/explorer/chain/staking_pool.ex
  24. 64
      apps/explorer/lib/explorer/chain/staking_pools_delegator.ex
  25. 120
      apps/explorer/lib/explorer/etherscan/logs.ex
  26. 51
      apps/explorer/lib/explorer/staking/pools_reader.ex
  27. 23
      apps/explorer/lib/explorer/token/metadata_retriever.ex
  28. 27
      apps/explorer/priv/repo/migrations/20190521104412_create_staking_pools.exs
  29. 26
      apps/explorer/priv/repo/migrations/20190523112839_create_staking_pools_delegators.exs
  30. 32
      apps/explorer/test/explorer/chain/import/runner/staking_pools_delegators_test.exs
  31. 19
      apps/explorer/test/explorer/chain/import/runner/staking_pools_test.exs
  32. 18
      apps/explorer/test/explorer/chain/staking_pool_test.exs
  33. 18
      apps/explorer/test/explorer/chain/staking_pools_delegator_test.exs
  34. 78
      apps/explorer/test/explorer/chain_test.exs
  35. 38
      apps/explorer/test/explorer/etherscan/logs_test.exs
  36. 170
      apps/explorer/test/explorer/staking/pools_reader_test.exs
  37. 47
      apps/explorer/test/explorer/token/metadata_retriever_test.exs
  38. 48
      apps/explorer/test/support/factory.ex
  39. 43
      apps/indexer/lib/indexer/fetcher/staking_pools.ex
  40. 8
      apps/indexer/test/indexer/fetcher/staking_pools_test.exs
  41. 0
      docs/.gitkeep
  42. 0
      docs/.nojekyll
  43. 323
      docs/README.md
  44. 21
      docs/index.html

@ -6,22 +6,23 @@
- [#1928](https://github.com/poanetwork/blockscout/pull/1928) - pagination styles were updated - [#1928](https://github.com/poanetwork/blockscout/pull/1928) - pagination styles were updated
- [#1940](https://github.com/poanetwork/blockscout/pull/1940) - qr modal button and background issue - [#1940](https://github.com/poanetwork/blockscout/pull/1940) - qr modal button and background issue
- [#1907](https://github.com/poanetwork/blockscout/pull/1907) - dropdown color bug fix (lukso theme) and tooltip color bug fix - [#1907](https://github.com/poanetwork/blockscout/pull/1907) - dropdown color bug fix (lukso theme) and tooltip color bug fix
- [#1857](https://github.com/poanetwork/blockscout/pull/1857) - Re-implement Geth JS internal transaction tracer in Elixir
- [#1859](https://github.com/poanetwork/blockscout/pull/1859) - feat: show raw transaction traces - [#1859](https://github.com/poanetwork/blockscout/pull/1859) - feat: show raw transaction traces
- [#1941](https://github.com/poanetwork/blockscout/pull/1941) - feat: add on demand fetching and stale attr to rpc - [#1941](https://github.com/poanetwork/blockscout/pull/1941) - feat: add on demand fetching and stale attr to rpc
- [#1957](https://github.com/poanetwork/blockscout/pull/1957) - Calculate stakes ratio before insert pools - [#1957](https://github.com/poanetwork/blockscout/pull/1957) - Calculate stakes ratio before insert pools
- [#1956](https://github.com/poanetwork/blockscout/pull/1956) - add logs tab to address - [#1956](https://github.com/poanetwork/blockscout/pull/1956) - add logs tab to address
- [#1952](https://github.com/poanetwork/blockscout/pull/1952) - feat: exclude empty contracts by default - [#1952](https://github.com/poanetwork/blockscout/pull/1952) - feat: exclude empty contracts by default
- [#1989](https://github.com/poanetwork/blockscout/pull/1989) - fix: consolidate address w/ balance one at a time
- [#1954](https://github.com/poanetwork/blockscout/pull/1954) - feat: use creation init on self destruct - [#1954](https://github.com/poanetwork/blockscout/pull/1954) - feat: use creation init on self destruct
- [#2036](https://github.com/poanetwork/blockscout/pull/2036) - New tables for staking pools and delegators
- [#1974](https://github.com/poanetwork/blockscout/pull/1974) - feat: previous page button logic - [#1974](https://github.com/poanetwork/blockscout/pull/1974) - feat: previous page button logic
- [#1999](https://github.com/poanetwork/blockscout/pull/1999) - load data async on addresses page - [#1999](https://github.com/poanetwork/blockscout/pull/1999) - load data async on addresses page
- [#2002](https://github.com/poanetwork/blockscout/pull/2002) - Get estimated count of blocks when cache is empty
- [#1807](https://github.com/poanetwork/blockscout/pull/1807) - New theming capabilites. - [#1807](https://github.com/poanetwork/blockscout/pull/1807) - New theming capabilites.
- [#2040](https://github.com/poanetwork/blockscout/pull/2040) - Verification links to other explorers for ETH - [#2040](https://github.com/poanetwork/blockscout/pull/2040) - Verification links to other explorers for ETH
- [#2037](https://github.com/poanetwork/blockscout/pull/2037) - add address logs search functionality
- [#2012](https://github.com/poanetwork/blockscout/pull/2012) - make all pages pagination async - [#2012](https://github.com/poanetwork/blockscout/pull/2012) - make all pages pagination async
### Fixes ### Fixes
- [#2066](https://github.com/poanetwork/blockscout/pull/2066) - fixed length of logs search input
- [#2056](https://github.com/poanetwork/blockscout/pull/2056) - log search form styles added
- [#2043](https://github.com/poanetwork/blockscout/pull/2043) - Fixed modal dialog width for 'verify other explorers' - [#2043](https://github.com/poanetwork/blockscout/pull/2043) - Fixed modal dialog width for 'verify other explorers'
- [#2025](https://github.com/poanetwork/blockscout/pull/2025) - Added a new color to display transactions' errors. - [#2025](https://github.com/poanetwork/blockscout/pull/2025) - Added a new color to display transactions' errors.
- [#2033](https://github.com/poanetwork/blockscout/pull/2033) - Header nav. dropdown active element color issue - [#2033](https://github.com/poanetwork/blockscout/pull/2033) - Header nav. dropdown active element color issue
@ -30,7 +31,6 @@
- [#1944](https://github.com/poanetwork/blockscout/pull/1944) - fixed styles for token's dropdown. - [#1944](https://github.com/poanetwork/blockscout/pull/1944) - fixed styles for token's dropdown.
- [#1926](https://github.com/poanetwork/blockscout/pull/1926) - status label alignment - [#1926](https://github.com/poanetwork/blockscout/pull/1926) - status label alignment
- [#1849](https://github.com/poanetwork/blockscout/pull/1849) - Improve chains menu - [#1849](https://github.com/poanetwork/blockscout/pull/1849) - Improve chains menu
- [#1869](https://github.com/poanetwork/blockscout/pull/1869) - Fix output and gas extraction in JS tracer for Geth
- [#1868](https://github.com/poanetwork/blockscout/pull/1868) - fix: logs list endpoint performance - [#1868](https://github.com/poanetwork/blockscout/pull/1868) - fix: logs list endpoint performance
- [#1822](https://github.com/poanetwork/blockscout/pull/1822) - Fix style breaks in decompiled contract code view - [#1822](https://github.com/poanetwork/blockscout/pull/1822) - Fix style breaks in decompiled contract code view
- [#1885](https://github.com/poanetwork/blockscout/pull/1885) - highlight reserved words in decompiled code - [#1885](https://github.com/poanetwork/blockscout/pull/1885) - highlight reserved words in decompiled code
@ -40,13 +40,15 @@
- [#1915](https://github.com/poanetwork/blockscout/pull/1915) - fallback to 2 latest evm versions - [#1915](https://github.com/poanetwork/blockscout/pull/1915) - fallback to 2 latest evm versions
- [#1937](https://github.com/poanetwork/blockscout/pull/1937) - Check the presence of overlap[i] object before retrieving properties from it - [#1937](https://github.com/poanetwork/blockscout/pull/1937) - Check the presence of overlap[i] object before retrieving properties from it
- [#1960](https://github.com/poanetwork/blockscout/pull/1960) - do not remove bold text in decompiled contacts - [#1960](https://github.com/poanetwork/blockscout/pull/1960) - do not remove bold text in decompiled contacts
- [#1992](https://github.com/poanetwork/blockscout/pull/1992) - fix: support https for wobserver polling
- [#1966](https://github.com/poanetwork/blockscout/pull/1966) - fix: add fields for contract filter performance - [#1966](https://github.com/poanetwork/blockscout/pull/1966) - fix: add fields for contract filter performance
- [#2017](https://github.com/poanetwork/blockscout/pull/2017) - fix: fix to/from filters on tx list pages - [#2017](https://github.com/poanetwork/blockscout/pull/2017) - fix: fix to/from filters on tx list pages
- [#2008](https://github.com/poanetwork/blockscout/pull/2008) - add new function clause for xDai network beneficiaries - [#2008](https://github.com/poanetwork/blockscout/pull/2008) - add new function clause for xDai network beneficiaries
- [#2009](https://github.com/poanetwork/blockscout/pull/2009) - addresses page improvements - [#2009](https://github.com/poanetwork/blockscout/pull/2009) - addresses page improvements
- [#2027](https://github.com/poanetwork/blockscout/pull/2027) - fix: `BlocksTransactionsMismatch` ignoring blocks without transactions - [#2027](https://github.com/poanetwork/blockscout/pull/2027) - fix: `BlocksTransactionsMismatch` ignoring blocks without transactions
- [#2062](https://github.com/poanetwork/blockscout/pull/2062) - fix: uniq by hash, instead of transaction - [#2062](https://github.com/poanetwork/blockscout/pull/2062) - fix: uniq by hash, instead of transaction
- [#2052](https://github.com/poanetwork/blockscout/pull/2052) - allow bytes32 for name and symbol
- [#2047](https://github.com/poanetwork/blockscout/pull/2047) - fix: show creating internal transactions
- [#2014](https://github.com/poanetwork/blockscout/pull/2014) - fix: use better queries for listLogs endpoint
### Chore ### Chore
@ -58,11 +60,25 @@
- [#2000](https://github.com/poanetwork/blockscout/pull/2000) - docker/Makefile: always set a container name - [#2000](https://github.com/poanetwork/blockscout/pull/2000) - docker/Makefile: always set a container name
- [#2018](https://github.com/poanetwork/blockscout/pull/2018) - Use PORT env variable in dev config - [#2018](https://github.com/poanetwork/blockscout/pull/2018) - Use PORT env variable in dev config
- [#2055](https://github.com/poanetwork/blockscout/pull/2055) - Increase timeout for geth indexers - [#2055](https://github.com/poanetwork/blockscout/pull/2055) - Increase timeout for geth indexers
- [#2069](https://github.com/poanetwork/blockscout/pull/2069) - Docsify integration: static docs page generation
## 1.3.14-beta ## 1.3.15-beta
### Features ### Features
- [#1857](https://github.com/poanetwork/blockscout/pull/1857) - Re-implement Geth JS internal transaction tracer in Elixir
- [#1989](https://github.com/poanetwork/blockscout/pull/1989) - fix: consolidate address w/ balance one at a time
- [#2002](https://github.com/poanetwork/blockscout/pull/2002) - Get estimated count of blocks when cache is empty
### Fixes
- [#1869](https://github.com/poanetwork/blockscout/pull/1869) - Fix output and gas extraction in JS tracer for Geth
- [#1992](https://github.com/poanetwork/blockscout/pull/1992) - fix: support https for wobserver polling
- [#2027](https://github.com/poanetwork/blockscout/pull/2027) - fix: `BlocksTransactionsMismatch` ignoring blocks without transactions
## 1.3.14-beta
- [#1812](https://github.com/poanetwork/blockscout/pull/1812) - add pagination to addresses page - [#1812](https://github.com/poanetwork/blockscout/pull/1812) - add pagination to addresses page
- [#1920](https://github.com/poanetwork/blockscout/pull/1920) - fix: remove source code fields from list endpoint - [#1920](https://github.com/poanetwork/blockscout/pull/1920) - fix: remove source code fields from list endpoint
- [#1876](https://github.com/poanetwork/blockscout/pull/1876) - async calculate a count of blocks - [#1876](https://github.com/poanetwork/blockscout/pull/1876) - async calculate a count of blocks

@ -122,7 +122,7 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
@import "components/alerts"; @import "components/alerts";
@import "components/verify_other_explorers"; @import "components/verify_other_explorers";
@import "components/errors"; @import "components/errors";
@import "components/log-search";
:export { :export {
dashboardBannerChartAxisFontColor: $dashboard-banner-chart-axis-font-color; dashboardBannerChartAxisFontColor: $dashboard-banner-chart-axis-font-color;
dashboardLineColorMarket: $dashboard-line-color-market; dashboardLineColorMarket: $dashboard-line-color-market;

@ -0,0 +1,64 @@
.logs-topbar {
padding-bottom: 30px;
@media (min-width: 600px) {
display: flex;
justify-content: space-between;
}
.pagination-container.position-top {
padding-top: 0 !important;
}
}
.logs-search {
display: flex;
position: relative;
@media (max-width: 599px) {
margin-bottom: 30px;
}
}
.logs-search-input, .logs-search-btn, .logs-search-btn-cancel {
height: 24px;
background-color: #f5f6fa;
border: 1px solid #f5f6fa;
color: #333;
border-radius: 2px;
outline: none;
font-family: Nunito, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-size: 12px;
font-weight: 600;
}
.logs-search-input {
padding-left: 6px;
display: inline-flex;
flex-grow: 2;
min-width: 160px;
&::placeholder {
color: #a3a9b5;
}
}
.logs-search-btn {
margin-left: 6px;
color: #a3a9b5;
transition: .1s ease-in;
cursor: pointer;
&:hover {
background-color: $primary;
color: #fff;
border-color: $primary;
}
}
.logs-search-btn-cancel {
color: #a3a9b5;
cursor: pointer;
transition: .1s ease-in;
position: absolute;
top: 0;
left: 136px;
&:hover {
color: #333;
}
}

@ -23,6 +23,7 @@ import './locale'
import './pages/address' import './pages/address'
import './pages/address/coin_balances' import './pages/address/coin_balances'
import './pages/address/transactions' import './pages/address/transactions'
import './pages/address/logs'
import './pages/address/validations' import './pages/address/validations'
import './pages/address/internal_transactions' import './pages/address/internal_transactions'
import './pages/blocks' import './pages/blocks'

@ -105,7 +105,9 @@ export function asyncReducer (state = asyncInitialState, action) {
state.pagesStack.push(window.location.href.split('?')[0]) state.pagesStack.push(window.location.href.split('?')[0])
} }
state.pagesStack.push(state.nextPagePath) if (state.pagesStack[state.pagesStack.length - 1] !== state.nextPagePath) {
state.pagesStack.push(state.nextPagePath)
}
return Object.assign({}, state, { beyondPageOne: true }) return Object.assign({}, state, { beyondPageOne: true })
} }
@ -279,12 +281,14 @@ function firstPageLoad (store) {
event.preventDefault() event.preventDefault()
loadItemsNext() loadItemsNext()
store.dispatch({type: 'NAVIGATE_TO_OLDER'}) store.dispatch({type: 'NAVIGATE_TO_OLDER'})
event.stopImmediatePropagation()
}) })
$element.on('click', '[data-prev-page-button]', (event) => { $element.on('click', '[data-prev-page-button]', (event) => {
event.preventDefault() event.preventDefault()
loadItemsPrev() loadItemsPrev()
store.dispatch({type: 'NAVIGATE_TO_NEWER'}) store.dispatch({type: 'NAVIGATE_TO_NEWER'})
event.stopImmediatePropagation()
}) })
} }

@ -0,0 +1,75 @@
import $ from 'jquery'
import _ from 'lodash'
import humps from 'humps'
import { connectElements } from '../../lib/redux_helpers.js'
import { createAsyncLoadStore } from '../../lib/async_listing_load'
export const initialState = {
addressHash: null,
isSearch: false
}
export function reducer (state, action) {
switch (action.type) {
case 'PAGE_LOAD':
case 'ELEMENTS_LOAD': {
return Object.assign({}, state, _.omit(action, 'type'))
}
case 'START_SEARCH': {
return Object.assign({}, state, {pagesStack: [], isSearch: true})
}
default:
return state
}
}
const elements = {
'[data-search-field]': {
render ($el, state) {
return $el
}
},
'[data-search-button]': {
render ($el, state) {
return $el
}
},
'[data-cancel-search-button]': {
render ($el, state) {
if (!state.isSearch) {
return $el.hide()
}
return $el.show()
}
}
}
if ($('[data-page="address-logs"]').length) {
const store = createAsyncLoadStore(reducer, initialState, 'dataset.identifierLog')
const addressHash = $('[data-page="address-details"]')[0].dataset.pageAddressHash
const $element = $('[data-async-listing]')
connectElements({ store, elements })
store.dispatch({
type: 'PAGE_LOAD',
addressHash: addressHash})
$element.on('click', '[data-search-button]', (event) => {
store.dispatch({
type: 'START_SEARCH',
addressHash: addressHash})
var topic = $('[data-search-field]').val()
var path = '/search_logs?topic=' + topic + '&address_id=' + store.getState().addressHash
store.dispatch({type: 'START_REQUEST'})
$.getJSON(path, {type: 'JSON'})
.done(response => store.dispatch(Object.assign({type: 'ITEMS_FETCHED'}, humps.camelizeKeys(response))))
.fail(() => store.dispatch({type: 'REQUEST_ERROR'}))
.always(() => store.dispatch({type: 'FINISH_REQUEST'}))
})
$element.on('click', '[data-cancel-search-button]', (event) => {
window.location.replace(window.location.href.split('?')[0])
})
}

@ -71,4 +71,48 @@ defmodule BlockScoutWeb.AddressLogsController do
not_found(conn) not_found(conn)
end end
end end
def search_logs(conn, %{"topic" => topic, "address_id" => address_hash_string} = params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do
topic = String.trim(topic)
formatted_topic = if String.starts_with?(topic, "0x"), do: topic, else: "0x" <> topic
logs_plus_one = Chain.address_to_logs(address, topic: formatted_topic)
{results, next_page} = split_list_by_page(logs_plus_one)
next_page_url =
case next_page_params(next_page, results, params) do
nil ->
nil
next_page_params ->
address_logs_path(conn, :index, address, Map.delete(next_page_params, "type"))
end
items =
results
|> Enum.map(fn log ->
View.render_to_string(
AddressLogsView,
"_logs.html",
log: log,
conn: conn
)
end)
json(
conn,
%{
items: items,
next_page_path: next_page_url
}
)
else
_ ->
not_found(conn)
end
end
end end

@ -10,18 +10,7 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do
def index(conn, %{"transaction_id" => hash_string, "type" => "JSON"} = params) do def index(conn, %{"transaction_id" => hash_string, "type" => "JSON"} = params) do
with {:ok, hash} <- Chain.string_to_transaction_hash(hash_string), with {:ok, hash} <- Chain.string_to_transaction_hash(hash_string),
{:ok, transaction} <- {:ok, transaction} <- Chain.hash_to_transaction(hash) do
Chain.hash_to_transaction(
hash,
necessity_by_association: %{
:block => :optional,
[created_contract_address: :names] => :optional,
[from_address: :names] => :optional,
[to_address: :names] => :optional,
[to_address: :smart_contract] => :optional,
:token_transfers => :optional
}
) do
full_options = full_options =
Keyword.merge( Keyword.merge(
[ [

@ -238,6 +238,8 @@ defmodule BlockScoutWeb.Router do
get("/search", ChainController, :search) get("/search", ChainController, :search)
get("/search_logs", AddressLogsController, :search_logs)
get("/token_autocomplete", ChainController, :token_autocomplete) get("/token_autocomplete", ChainController, :token_autocomplete)
get("/chain_blocks", ChainController, :chain_blocks, as: :chain_blocks) get("/chain_blocks", ChainController, :chain_blocks, as: :chain_blocks)

@ -1,4 +1,4 @@
<div data-test="transaction_log" class="tile tile-muted"> <div data-test="address_log" class="tile tile-muted" data-identifier-log="<%= "#{to_string(@log.transaction.hash)}#{@log.index}" %>">
<dl class="row"> <dl class="row">
<dt class="col-md-2"> <%= gettext "Transaction" %> </dt> <dt class="col-md-2"> <%= gettext "Transaction" %> </dt>
<dd class="col-md-10"> <dd class="col-md-10">

@ -1,12 +1,21 @@
<section class="container"> <section class="container">
<%= render BlockScoutWeb.AddressView, "overview.html", assigns %> <%= render BlockScoutWeb.AddressView, "overview.html", assigns %>
<section data-page="address-logs">
<div class="card"> <div class="card">
<%= render BlockScoutWeb.AddressView, "_tabs.html", assigns %> <%= render BlockScoutWeb.AddressView, "_tabs.html", assigns %>
<div class="card-body" data-async-load data-async-listing="<%= @current_path %>"> <div class="card-body" data-async-load data-async-listing="<%= @current_path %>">
<h2 class="card-title"><%= gettext "Logs" %></h2> <h2 class="card-title"><%= gettext "Logs" %></h2>
<div class="logs-topbar">
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> <div class="logs-search">
<input data-search-field class="logs-search-input" type='text' placeholder=<%= gettext("Topic") %> id='search-text-input' />
<button data-cancel-search-button class="logs-search-btn-cancel" type="button">x</button>
<button data-search-button class="logs-search-btn" type="button"><%= gettext("Search") %></button>
</div>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
</div>
<button data-error-message class="alert alert-danger col-12 text-left" style="display: none;"> <button data-error-message class="alert alert-danger col-12 text-left" style="display: none;">
<span href="#" class="alert-link"><%= gettext("Something went wrong, click to reload.") %></span> <span href="#" class="alert-link"><%= gettext("Something went wrong, click to reload.") %></span>
@ -25,4 +34,5 @@
</div> </div>
</div> </div>
</section>
</section> </section>

@ -500,7 +500,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:26 #: lib/block_scout_web/templates/address/_tabs.html.eex:26
#: lib/block_scout_web/templates/address_logs/index.html.eex:7 #: lib/block_scout_web/templates/address_logs/index.html.eex:8
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:17 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:17
#: lib/block_scout_web/templates/transaction_log/index.html.eex:8 #: lib/block_scout_web/templates/transaction_log/index.html.eex:8
#: lib/block_scout_web/views/address_view.ex:314 #: lib/block_scout_web/views/address_view.ex:314
@ -674,6 +674,7 @@ msgid "Responses"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:14
#: lib/block_scout_web/templates/layout/_topnav.html.eex:111 #: lib/block_scout_web/templates/layout/_topnav.html.eex:111
#: lib/block_scout_web/templates/layout/_topnav.html.eex:128 #: lib/block_scout_web/templates/layout/_topnav.html.eex:128
msgid "Search" msgid "Search"
@ -1217,7 +1218,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:34 #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:34
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:61 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:61
#: lib/block_scout_web/templates/address_logs/index.html.eex:12 #: lib/block_scout_web/templates/address_logs/index.html.eex:21
#: lib/block_scout_web/templates/address_token/index.html.eex:13 #: lib/block_scout_web/templates/address_token/index.html.eex:13
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:20 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:20
#: lib/block_scout_web/templates/address_transaction/index.html.eex:57 #: lib/block_scout_web/templates/address_transaction/index.html.eex:57
@ -1682,7 +1683,7 @@ msgid "Displaying the init data provided of the creating transaction."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:17 #: lib/block_scout_web/templates/address_logs/index.html.eex:26
msgid "There are no logs for this address." msgid "There are no logs for this address."
msgstr "" msgstr ""
@ -1720,3 +1721,8 @@ msgstr ""
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:17 #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:17
msgid "There are no token transfers for this transaction" msgid "There are no token transfers for this transaction"
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:12
msgid "Topic"
msgstr ""

@ -145,7 +145,7 @@ msgid "Block %{block_number} - %{subnetwork} Explorer"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:73 #: lib/block_scout_web/templates/transaction/overview.html.eex:72
msgid "Block Confirmations" msgid "Block Confirmations"
msgstr "" msgstr ""
@ -160,7 +160,7 @@ msgid "Block Mined, awaiting import..."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:59 #: lib/block_scout_web/templates/transaction/overview.html.eex:58
msgid "Block Number" msgid "Block Number"
msgstr "" msgstr ""
@ -190,7 +190,7 @@ msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:32 #: lib/block_scout_web/templates/address/_tabs.html.eex:32
#: lib/block_scout_web/templates/address/overview.html.eex:95 #: lib/block_scout_web/templates/address/overview.html.eex:95
#: lib/block_scout_web/templates/address_validation/index.html.eex:13 #: lib/block_scout_web/templates/address_validation/index.html.eex:13
#: lib/block_scout_web/views/address_view.ex:308 #: lib/block_scout_web/views/address_view.ex:313
msgid "Blocks Validated" msgid "Blocks Validated"
msgstr "" msgstr ""
@ -207,8 +207,8 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:37 #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:37
#: lib/block_scout_web/templates/address/overview.html.eex:141 #: lib/block_scout_web/templates/address/overview.html.eex:142
#: lib/block_scout_web/templates/address/overview.html.eex:149 #: lib/block_scout_web/templates/address/overview.html.eex:150
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:106 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:106
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:114 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:114
msgid "Close" msgid "Close"
@ -218,7 +218,7 @@ msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:42 #: lib/block_scout_web/templates/address/_tabs.html.eex:42
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:165 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:165
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187
#: lib/block_scout_web/views/address_view.ex:304 #: lib/block_scout_web/views/address_view.ex:309
msgid "Code" msgid "Code"
msgstr "" msgstr ""
@ -382,7 +382,7 @@ msgstr ""
#: lib/block_scout_web/templates/layout/app.html.eex:55 #: lib/block_scout_web/templates/layout/app.html.eex:55
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:20 #: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:20
#: lib/block_scout_web/templates/transaction/_tile.html.eex:30 #: lib/block_scout_web/templates/transaction/_tile.html.eex:30
#: lib/block_scout_web/templates/transaction/overview.html.eex:210 #: lib/block_scout_web/templates/transaction/overview.html.eex:209
#: lib/block_scout_web/views/wei_helpers.ex:72 #: lib/block_scout_web/views/wei_helpers.ex:72
msgid "Ether" msgid "Ether"
msgstr "POA" msgstr "POA"
@ -452,7 +452,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:39 #: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:39
#: lib/block_scout_web/templates/transaction/_tile.html.eex:76 #: lib/block_scout_web/templates/transaction/_tile.html.eex:74
msgid "IN" msgid "IN"
msgstr "" msgstr ""
@ -476,7 +476,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:19 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:19
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:11 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:11
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6 #: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6
#: lib/block_scout_web/views/address_view.ex:303 #: lib/block_scout_web/views/address_view.ex:308
#: lib/block_scout_web/views/transaction_view.ex:339 #: lib/block_scout_web/views/transaction_view.ex:339
msgid "Internal Transactions" msgid "Internal Transactions"
msgstr "" msgstr ""
@ -494,16 +494,16 @@ msgid "Less than"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:238 #: lib/block_scout_web/templates/transaction/overview.html.eex:237
msgid "Limit" msgid "Limit"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:26 #: lib/block_scout_web/templates/address/_tabs.html.eex:26
#: lib/block_scout_web/templates/address_logs/index.html.eex:7 #: lib/block_scout_web/templates/address_logs/index.html.eex:8
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:17 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:17
#: lib/block_scout_web/templates/transaction_log/index.html.eex:8 #: lib/block_scout_web/templates/transaction_log/index.html.eex:8
#: lib/block_scout_web/views/address_view.ex:309 #: lib/block_scout_web/views/address_view.ex:314
#: lib/block_scout_web/views/transaction_view.ex:340 #: lib/block_scout_web/views/transaction_view.ex:340
msgid "Logs" msgid "Logs"
msgstr "" msgstr ""
@ -574,13 +574,13 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/block/overview.html.eex:73 #: lib/block_scout_web/templates/block/overview.html.eex:73
#: lib/block_scout_web/templates/transaction/overview.html.eex:80 #: lib/block_scout_web/templates/transaction/overview.html.eex:79
msgid "Nonce" msgid "Nonce"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:37 #: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:37
#: lib/block_scout_web/templates/transaction/_tile.html.eex:72 #: lib/block_scout_web/templates/transaction/_tile.html.eex:70
msgid "OUT" msgid "OUT"
msgstr "" msgstr ""
@ -634,7 +634,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:33 #: lib/block_scout_web/templates/address/overview.html.eex:33
#: lib/block_scout_web/templates/address/overview.html.eex:140 #: lib/block_scout_web/templates/address/overview.html.eex:141
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:35 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:35
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:105 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:105
msgid "QR Code" msgid "QR Code"
@ -648,7 +648,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:58 #: lib/block_scout_web/templates/address/_tabs.html.eex:58
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:25 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:25
#: lib/block_scout_web/views/address_view.ex:306 #: lib/block_scout_web/views/address_view.ex:311
#: lib/block_scout_web/views/tokens/overview_view.ex:37 #: lib/block_scout_web/views/tokens/overview_view.ex:37
msgid "Read Contract" msgid "Read Contract"
msgstr "" msgstr ""
@ -674,6 +674,7 @@ msgid "Responses"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:14
#: lib/block_scout_web/templates/layout/_topnav.html.eex:111 #: lib/block_scout_web/templates/layout/_topnav.html.eex:111
#: lib/block_scout_web/templates/layout/_topnav.html.eex:128 #: lib/block_scout_web/templates/layout/_topnav.html.eex:128
msgid "Search" msgid "Search"
@ -705,7 +706,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:21 #: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:21
#: lib/block_scout_web/templates/transaction/_tile.html.eex:34 #: lib/block_scout_web/templates/transaction/_tile.html.eex:34
#: lib/block_scout_web/templates/transaction/overview.html.eex:85 #: lib/block_scout_web/templates/transaction/overview.html.eex:84
msgid "TX Fee" msgid "TX Fee"
msgstr "" msgstr ""
@ -802,8 +803,8 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:5 #: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:5
#: lib/block_scout_web/templates/transaction/overview.html.eex:180 #: lib/block_scout_web/templates/transaction/overview.html.eex:179
#: lib/block_scout_web/templates/transaction/overview.html.eex:194 #: lib/block_scout_web/templates/transaction/overview.html.eex:193
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:4 #: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:4
#: lib/block_scout_web/views/transaction_view.ex:284 #: lib/block_scout_web/views/transaction_view.ex:284
msgid "Token Transfer" msgid "Token Transfer"
@ -823,7 +824,7 @@ msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:8 #: lib/block_scout_web/templates/address/_tabs.html.eex:8
#: lib/block_scout_web/templates/address_token/index.html.eex:8 #: lib/block_scout_web/templates/address_token/index.html.eex:8
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:9 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:9
#: lib/block_scout_web/views/address_view.ex:301 #: lib/block_scout_web/views/address_view.ex:306
msgid "Tokens" msgid "Tokens"
msgstr "" msgstr ""
@ -881,7 +882,7 @@ msgstr ""
#: lib/block_scout_web/templates/block_transaction/index.html.eex:18 #: lib/block_scout_web/templates/block_transaction/index.html.eex:18
#: lib/block_scout_web/templates/chain/show.html.eex:108 #: lib/block_scout_web/templates/chain/show.html.eex:108
#: lib/block_scout_web/templates/layout/_topnav.html.eex:35 #: lib/block_scout_web/templates/layout/_topnav.html.eex:35
#: lib/block_scout_web/views/address_view.ex:302 #: lib/block_scout_web/views/address_view.ex:307
msgid "Transactions" msgid "Transactions"
msgstr "" msgstr ""
@ -917,7 +918,7 @@ msgid "Unique Token"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:232 #: lib/block_scout_web/templates/transaction/overview.html.eex:231
msgid "Used" msgid "Used"
msgstr "" msgstr ""
@ -937,7 +938,7 @@ msgid "Validations"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:210 #: lib/block_scout_web/templates/transaction/overview.html.eex:209
msgid "Value" msgid "Value"
msgstr "" msgstr ""
@ -958,12 +959,12 @@ msgid "View Contract"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_tile.html.eex:56 #: lib/block_scout_web/templates/transaction/_tile.html.eex:55
msgid "View Less Transfers" msgid "View Less Transfers"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_tile.html.eex:55 #: lib/block_scout_web/templates/transaction/_tile.html.eex:54
msgid "View More Transfers" msgid "View More Transfers"
msgstr "" msgstr ""
@ -1101,7 +1102,7 @@ msgid "This API is provided for developers transitioning their applications from
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:110 #: lib/block_scout_web/templates/transaction/overview.html.eex:109
msgid "Raw Input" msgid "Raw Input"
msgstr "" msgstr ""
@ -1217,7 +1218,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:34 #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:34
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:61 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:61
#: lib/block_scout_web/templates/address_logs/index.html.eex:12 #: lib/block_scout_web/templates/address_logs/index.html.eex:21
#: lib/block_scout_web/templates/address_token/index.html.eex:13 #: lib/block_scout_web/templates/address_token/index.html.eex:13
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:20 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:20
#: lib/block_scout_web/templates/address_transaction/index.html.eex:57 #: lib/block_scout_web/templates/address_transaction/index.html.eex:57
@ -1257,7 +1258,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:20 #: lib/block_scout_web/templates/address/_tabs.html.eex:20
#: lib/block_scout_web/views/address_view.ex:307 #: lib/block_scout_web/views/address_view.ex:312
msgid "Coin Balance History" msgid "Coin Balance History"
msgstr "" msgstr ""
@ -1499,18 +1500,18 @@ msgid "Transactions Sent"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:102 #: lib/block_scout_web/templates/transaction/overview.html.eex:101
msgid "Transaction Speed" msgid "Transaction Speed"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:116 #: lib/block_scout_web/templates/transaction/overview.html.eex:115
#: lib/block_scout_web/templates/transaction/overview.html.eex:120 #: lib/block_scout_web/templates/transaction/overview.html.eex:119
msgid "Hex (Default)" msgid "Hex (Default)"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:123 #: lib/block_scout_web/templates/transaction/overview.html.eex:122
msgid "UTF-8" msgid "UTF-8"
msgstr "" msgstr ""
@ -1561,7 +1562,7 @@ msgid "Copy Decompiled Contract Code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/address_view.ex:305 #: lib/block_scout_web/views/address_view.ex:310
msgid "Decompiled Code" msgid "Decompiled Code"
msgstr "" msgstr ""
@ -1586,12 +1587,12 @@ msgid "Optimization runs"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:180 #: lib/block_scout_web/templates/transaction/overview.html.eex:179
msgid "ERC-20" msgid "ERC-20"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:194 #: lib/block_scout_web/templates/transaction/overview.html.eex:193
msgid "ERC-721" msgid "ERC-721"
msgstr "" msgstr ""
@ -1611,7 +1612,7 @@ msgid "View All Transactions"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:228 #: lib/block_scout_web/templates/transaction/overview.html.eex:227
msgid "Gas" msgid "Gas"
msgstr "" msgstr ""
@ -1682,7 +1683,7 @@ msgid "Displaying the init data provided of the creating transaction."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:17 #: lib/block_scout_web/templates/address_logs/index.html.eex:26
msgid "There are no logs for this address." msgid "There are no logs for this address."
msgstr "" msgstr ""
@ -1720,3 +1721,8 @@ msgstr ""
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:17 #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:17
msgid "There are no token transfers for this transaction" msgid "There are no token transfers for this transaction"
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:12
msgid "Topic"
msgstr ""

@ -153,6 +153,49 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
end) end)
end end
test "returns internal an transaction that created the address", %{conn: conn} do
address = insert(:address)
transaction =
:transaction
|> insert()
|> with_block(insert(:block, number: 1))
from_internal_transaction =
insert(:internal_transaction,
transaction: transaction,
from_address: address,
index: 1,
block_number: transaction.block_number,
transaction_index: transaction.index
)
to_internal_transaction =
insert(:internal_transaction,
transaction: transaction,
to_address: nil,
created_contract_address: address,
index: 2,
block_number: transaction.block_number,
transaction_index: transaction.index
)
path = address_internal_transaction_path(conn, :index, address, %{"filter" => "to", "type" => "JSON"})
conn = get(conn, path)
internal_transaction_tiles = json_response(conn, 200)["items"]
assert Enum.any?(internal_transaction_tiles, fn tile ->
String.contains?(tile, to_string(to_internal_transaction.transaction_hash)) &&
String.contains?(tile, "data-internal-transaction-index=\"#{to_internal_transaction.index}\"")
end)
refute Enum.any?(internal_transaction_tiles, fn tile ->
String.contains?(tile, to_string(from_internal_transaction.transaction_hash)) &&
String.contains?(tile, "data-internal-transaction-index=\"#{from_internal_transaction.index}\"")
end)
end
test "returns next page of results based on last seen internal transaction", %{conn: conn} do test "returns next page of results based on last seen internal transaction", %{conn: conn} do
address = insert(:address) address = insert(:address)

@ -266,7 +266,7 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do
transaction = transaction =
%Transaction{block: block} = %Transaction{block: block} =
:transaction :transaction
|> insert() |> insert(to_address: contract_address)
|> with_block() |> with_block()
log = insert(:log, address: contract_address, transaction: transaction) log = insert(:log, address: contract_address, transaction: transaction)
@ -313,13 +313,13 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do
transaction_block1 = transaction_block1 =
%Transaction{} = %Transaction{} =
:transaction :transaction
|> insert() |> insert(to_address: contract_address)
|> with_block(first_block) |> with_block(first_block)
transaction_block2 = transaction_block2 =
%Transaction{} = %Transaction{} =
:transaction :transaction
|> insert() |> insert(to_address: contract_address)
|> with_block(second_block) |> with_block(second_block)
insert(:log, address: contract_address, transaction: transaction_block1) insert(:log, address: contract_address, transaction: transaction_block1)
@ -356,13 +356,13 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do
transaction_block1 = transaction_block1 =
%Transaction{} = %Transaction{} =
:transaction :transaction
|> insert() |> insert(to_address: contract_address)
|> with_block(first_block) |> with_block(first_block)
transaction_block2 = transaction_block2 =
%Transaction{} = %Transaction{} =
:transaction :transaction
|> insert() |> insert(to_address: contract_address)
|> with_block(second_block) |> with_block(second_block)
insert(:log, address: contract_address, transaction: transaction_block1) insert(:log, address: contract_address, transaction: transaction_block1)

@ -28,10 +28,11 @@ defmodule EthereumJSONRPC.Contract do
@spec execute_contract_functions([call()], [map()], EthereumJSONRPC.json_rpc_named_arguments()) :: [call_result()] @spec execute_contract_functions([call()], [map()], EthereumJSONRPC.json_rpc_named_arguments()) :: [call_result()]
def execute_contract_functions(requests, abi, json_rpc_named_arguments) do def execute_contract_functions(requests, abi, json_rpc_named_arguments) do
functions = parsed_abi =
abi abi
|> ABI.parse_specification() |> ABI.parse_specification()
|> Enum.into(%{}, &{&1.function, &1})
functions = Enum.into(parsed_abi, %{}, &{&1.function, &1})
requests_with_index = Enum.with_index(requests) requests_with_index = Enum.with_index(requests)
@ -52,13 +53,15 @@ defmodule EthereumJSONRPC.Contract do
|> Enum.into(%{}, &{&1.id, &1}) |> Enum.into(%{}, &{&1.id, &1})
Enum.map(requests_with_index, fn {%{function_name: function_name}, index} -> Enum.map(requests_with_index, fn {%{function_name: function_name}, index} ->
selectors = Enum.filter(parsed_abi, fn p_abi -> p_abi.function == function_name end)
indexed_responses[index] indexed_responses[index]
|> case do |> case do
nil -> nil ->
{:error, "No result"} {:error, "No result"}
response -> response ->
{^index, result} = Encoder.decode_result(response, functions[function_name]) {^index, result} = Encoder.decode_result(response, selectors)
result result
end end
end) end)

@ -35,12 +35,29 @@ defmodule EthereumJSONRPC.Encoder do
@doc """ @doc """
Given a result from the blockchain, and the function selector, returns the result decoded. Given a result from the blockchain, and the function selector, returns the result decoded.
""" """
@spec decode_result(map(), %ABI.FunctionSelector{}) :: @spec decode_result(map(), %ABI.FunctionSelector{} | [%ABI.FunctionSelector{}]) ::
{String.t(), {:ok, any()} | {:error, String.t() | :invalid_data}} {String.t(), {:ok, any()} | {:error, String.t() | :invalid_data}}
def decode_result(%{error: %{code: code, message: message}, id: id}, _selector) do def decode_result(%{error: %{code: code, message: message}, id: id}, _selector) do
{id, {:error, "(#{code}) #{message}"}} {id, {:error, "(#{code}) #{message}"}}
end end
def decode_result(result, selectors) when is_list(selectors) do
selectors
|> Enum.map(fn selector ->
try do
decode_result(result, selector)
rescue
_ -> :error
end
end)
|> Enum.find(fn decode ->
case decode do
{_id, {:ok, _}} -> true
_ -> false
end
end)
end
def decode_result(%{id: id, result: result}, function_selector) do def decode_result(%{id: id, result: result}, function_selector) do
types_list = List.wrap(function_selector.returns) types_list = List.wrap(function_selector.returns)

@ -39,6 +39,7 @@ defmodule Explorer.Chain do
InternalTransaction, InternalTransaction,
Log, Log,
SmartContract, SmartContract,
StakingPool,
Token, Token,
TokenTransfer, TokenTransfer,
Transaction, Transaction,
@ -280,7 +281,7 @@ defmodule Explorer.Chain do
|> Enum.take(paging_options.page_size) |> Enum.take(paging_options.page_size)
end end
@spec address_to_logs(Address.t(), [paging_options]) :: [ @spec address_to_logs(Address.t(), Keyword.t()) :: [
Log.t() Log.t()
] ]
def address_to_logs( def address_to_logs(
@ -292,7 +293,7 @@ defmodule Explorer.Chain do
{block_number, transaction_index, log_index} = paging_options.key || {BlockNumberCache.max_number(), 0, 0} {block_number, transaction_index, log_index} = paging_options.key || {BlockNumberCache.max_number(), 0, 0}
query = base_query =
from(log in Log, from(log in Log,
inner_join: transaction in assoc(log, :transaction), inner_join: transaction in assoc(log, :transaction),
order_by: [desc: transaction.block_number, desc: transaction.index], order_by: [desc: transaction.block_number, desc: transaction.index],
@ -307,11 +308,22 @@ defmodule Explorer.Chain do
select: log select: log
) )
query base_query
|> filter_topic(options)
|> Repo.all() |> Repo.all()
|> Enum.take(paging_options.page_size) |> Enum.take(paging_options.page_size)
end end
defp filter_topic(base_query, topic: topic) do
from(log in base_query,
where:
log.first_topic == ^topic or log.second_topic == ^topic or log.third_topic == ^topic or
log.fourth_topic == ^topic
)
end
defp filter_topic(base_query, _), do: base_query
@doc """ @doc """
Finds all `t:Explorer.Chain.Transaction.t/0`s given the address_hash and the token contract Finds all `t:Explorer.Chain.Transaction.t/0`s given the address_hash and the token contract
address hash. address hash.
@ -2925,7 +2937,7 @@ defmodule Explorer.Chain do
def staking_pools(filter, %PagingOptions{page_size: page_size, page_number: page_number} \\ @default_paging_options) do def staking_pools(filter, %PagingOptions{page_size: page_size, page_number: page_number} \\ @default_paging_options) do
off = page_size * (page_number - 1) off = page_size * (page_number - 1)
Address.Name StakingPool
|> staking_pool_filter(filter) |> staking_pool_filter(filter)
|> limit(^page_size) |> limit(^page_size)
|> offset(^off) |> offset(^off)
@ -2935,55 +2947,36 @@ defmodule Explorer.Chain do
@doc "Get count of staking pools from the DB" @doc "Get count of staking pools from the DB"
@spec staking_pools_count(filter :: :validator | :active | :inactive) :: integer @spec staking_pools_count(filter :: :validator | :active | :inactive) :: integer
def staking_pools_count(filter) do def staking_pools_count(filter) do
Address.Name StakingPool
|> staking_pool_filter(filter) |> staking_pool_filter(filter)
|> Repo.aggregate(:count, :address_hash) |> Repo.aggregate(:count, :staking_address_hash)
end end
defp staking_pool_filter(query, :validator) do defp staking_pool_filter(query, :validator) do
where( where(
query, query,
[address], [pool],
fragment( pool.is_active == true and
""" pool.is_deleted == false and
(?->>'is_active')::boolean = true and pool.is_validator == true
(?->>'deleted')::boolean is not true and
(?->>'is_validator')::boolean = true
""",
address.metadata,
address.metadata,
address.metadata
)
) )
end end
defp staking_pool_filter(query, :active) do defp staking_pool_filter(query, :active) do
where( where(
query, query,
[address], [pool],
fragment( pool.is_active == true and
""" pool.is_deleted == false
(?->>'is_active')::boolean = true and
(?->>'deleted')::boolean is not true
""",
address.metadata,
address.metadata
)
) )
end end
defp staking_pool_filter(query, :inactive) do defp staking_pool_filter(query, :inactive) do
where( where(
query, query,
[address], [pool],
fragment( pool.is_active == false and
""" pool.is_deleted == false
(?->>'is_active')::boolean = false and
(?->>'deleted')::boolean is not true
""",
address.metadata,
address.metadata
)
) )
end end

@ -1,12 +1,12 @@
defmodule Explorer.Chain.Import.Runner.StakingPools do defmodule Explorer.Chain.Import.Runner.StakingPools do
@moduledoc """ @moduledoc """
Bulk imports staking pools to Address.Name tabe. Bulk imports staking pools to StakingPool tabe.
""" """
require Ecto.Query require Ecto.Query
alias Ecto.{Changeset, Multi, Repo} alias Ecto.{Changeset, Multi, Repo}
alias Explorer.Chain.{Address, Import} alias Explorer.Chain.{Import, StakingPool}
import Ecto.Query, only: [from: 2] import Ecto.Query, only: [from: 2]
@ -15,10 +15,10 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do
# milliseconds # milliseconds
@timeout 60_000 @timeout 60_000
@type imported :: [Address.Name.t()] @type imported :: [StakingPool.t()]
@impl Import.Runner @impl Import.Runner
def ecto_schema_module, do: Address.Name def ecto_schema_module, do: StakingPool
@impl Import.Runner @impl Import.Runner
def option_key, do: :staking_pools def option_key, do: :staking_pools
@ -47,23 +47,25 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do
|> Multi.run(:insert_staking_pools, fn repo, _ -> |> Multi.run(:insert_staking_pools, fn repo, _ ->
insert(repo, changes_list, insert_options) insert(repo, changes_list, insert_options)
end) end)
|> Multi.run(:calculate_stakes_ratio, fn repo, _ ->
calculate_stakes_ratio(repo, insert_options)
end)
end end
@impl Import.Runner @impl Import.Runner
def timeout, do: @timeout def timeout, do: @timeout
defp mark_as_deleted(repo, changes_list, %{timeout: timeout}) when is_list(changes_list) do defp mark_as_deleted(repo, changes_list, %{timeout: timeout}) when is_list(changes_list) do
addresses = Enum.map(changes_list, & &1.address_hash) addresses = Enum.map(changes_list, & &1.staking_address_hash)
query = query =
from( from(
address_name in Address.Name, pool in StakingPool,
where: where: pool.staking_address_hash not in ^addresses,
address_name.address_hash not in ^addresses and
fragment("(?->>'is_pool')::boolean = true", address_name.metadata),
update: [ update: [
set: [ set: [
metadata: fragment("? || '{\"deleted\": true}'::jsonb", address_name.metadata) is_deleted: true,
is_active: false
] ]
] ]
) )
@ -83,7 +85,7 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do
required(:timeout) => timeout, required(:timeout) => timeout,
required(:timestamps) => Import.timestamps() required(:timestamps) => Import.timestamps()
}) :: }) ::
{:ok, [Address.Name.t()]} {:ok, [StakingPool.t()]}
| {:error, [Changeset.t()]} | {:error, [Changeset.t()]}
defp insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do defp insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do
on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0)
@ -91,11 +93,11 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do
{:ok, _} = {:ok, _} =
Import.insert_changes_list( Import.insert_changes_list(
repo, repo,
stakes_ratio(changes_list), changes_list,
conflict_target: {:unsafe_fragment, "(address_hash) where \"primary\" = true"}, conflict_target: :staking_address_hash,
on_conflict: on_conflict, on_conflict: on_conflict,
for: Address.Name, for: StakingPool,
returning: [:address_hash], returning: [:staking_address_hash],
timeout: timeout, timeout: timeout,
timestamps: timestamps timestamps: timestamps
) )
@ -103,31 +105,58 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do
defp default_on_conflict do defp default_on_conflict do
from( from(
name in Address.Name, pool in StakingPool,
update: [ update: [
set: [ set: [
name: fragment("EXCLUDED.name"), mining_address_hash: fragment("EXCLUDED.mining_address_hash"),
metadata: fragment("EXCLUDED.metadata"), delegators_count: fragment("EXCLUDED.delegators_count"),
inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", name.inserted_at), is_active: fragment("EXCLUDED.is_active"),
updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", name.updated_at) is_banned: fragment("EXCLUDED.is_banned"),
is_validator: fragment("EXCLUDED.is_validator"),
likelihood: fragment("EXCLUDED.likelihood"),
staked_ratio: fragment("EXCLUDED.staked_ratio"),
self_staked_amount: fragment("EXCLUDED.self_staked_amount"),
staked_amount: fragment("EXCLUDED.staked_amount"),
was_banned_count: fragment("EXCLUDED.was_banned_count"),
was_validator_count: fragment("EXCLUDED.was_validator_count"),
is_deleted: fragment("EXCLUDED.is_deleted"),
inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", pool.inserted_at),
updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", pool.updated_at)
] ]
] ]
) )
end end
# Calculates staked ratio for each pool defp calculate_stakes_ratio(repo, %{timeout: timeout}) do
defp stakes_ratio(pools) do total_query =
active_pools = Enum.filter(pools, & &1.metadata[:is_active]) from(
pool in StakingPool,
stakes_total = where: pool.is_active == true,
Enum.reduce(pools, 0, fn pool, acc -> select: sum(pool.staked_amount)
acc + pool.metadata[:staked_amount] )
end)
Enum.map(active_pools, fn pool -> total = repo.one!(total_query)
staked_ratio = if stakes_total > 0, do: pool.metadata[:staked_amount] / stakes_total, else: 0
if total > Decimal.new(0) do
query =
from(
p in StakingPool,
where: p.is_active == true,
update: [
set: [
staked_ratio: p.staked_amount / ^total * 100,
likelihood: p.staked_amount / ^total * 100
]
]
)
put_in(pool, [:metadata, :staked_ratio], staked_ratio) {count, _} = repo.update_all(query, [], timeout: timeout)
end) {:ok, count}
else
{:ok, 1}
end
rescue
postgrex_error in Postgrex.Error ->
{:error, %{exception: postgrex_error}}
end end
end end

@ -0,0 +1,91 @@
defmodule Explorer.Chain.Import.Runner.StakingPoolsDelegators do
@moduledoc """
Bulk imports delegators to StakingPoolsDelegator tabe.
"""
require Ecto.Query
alias Ecto.{Changeset, Multi, Repo}
alias Explorer.Chain.{Import, StakingPoolsDelegator}
import Ecto.Query, only: [from: 2]
@behaviour Import.Runner
# milliseconds
@timeout 60_000
@type imported :: [StakingPoolsDelegator.t()]
@impl Import.Runner
def ecto_schema_module, do: StakingPoolsDelegator
@impl Import.Runner
def option_key, do: :staking_pools_delegators
@impl Import.Runner
def imported_table_row do
%{
value_type: "[#{ecto_schema_module()}.t()]",
value_description: "List of `t:#{ecto_schema_module()}.t/0`s"
}
end
@impl Import.Runner
def run(multi, changes_list, %{timestamps: timestamps} = options) do
insert_options =
options
|> Map.get(option_key(), %{})
|> Map.take(~w(on_conflict timeout)a)
|> Map.put_new(:timeout, @timeout)
|> Map.put(:timestamps, timestamps)
multi
|> Multi.run(:insert_staking_pools_delegators, fn repo, _ ->
insert(repo, changes_list, insert_options)
end)
end
@impl Import.Runner
def timeout, do: @timeout
@spec insert(Repo.t(), [map()], %{
optional(:on_conflict) => Import.Runner.on_conflict(),
required(:timeout) => timeout,
required(:timestamps) => Import.timestamps()
}) ::
{:ok, [StakingPoolsDelegator.t()]}
| {:error, [Changeset.t()]}
defp insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do
on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0)
{:ok, _} =
Import.insert_changes_list(
repo,
changes_list,
conflict_target: [:pool_address_hash, :delegator_address_hash],
on_conflict: on_conflict,
for: StakingPoolsDelegator,
returning: [:pool_address_hash, :delegator_address_hash],
timeout: timeout,
timestamps: timestamps
)
end
defp default_on_conflict do
from(
delegator in StakingPoolsDelegator,
update: [
set: [
stake_amount: fragment("EXCLUDED.stake_amount"),
ordered_withdraw: fragment("EXCLUDED.ordered_withdraw"),
max_withdraw_allowed: fragment("EXCLUDED.max_withdraw_allowed"),
max_ordered_withdraw_allowed: fragment("EXCLUDED.max_ordered_withdraw_allowed"),
ordered_withdraw_epoch: fragment("EXCLUDED.ordered_withdraw_epoch"),
inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", delegator.inserted_at),
updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", delegator.updated_at)
]
]
)
end
end

@ -25,7 +25,8 @@ defmodule Explorer.Chain.Import.Stage.AddressReferencing do
Runner.TokenTransfers, Runner.TokenTransfers,
Runner.Address.CurrentTokenBalances, Runner.Address.CurrentTokenBalances,
Runner.Address.TokenBalances, Runner.Address.TokenBalances,
Runner.StakingPools Runner.StakingPools,
Runner.StakingPoolsDelegators
] ]
@impl Stage @impl Stage

@ -470,7 +470,12 @@ defmodule Explorer.Chain.InternalTransaction do
from_address_hash, created_contract_address_hash from internal_transactions' table. from_address_hash, created_contract_address_hash from internal_transactions' table.
""" """
def where_address_fields_match(query, address_hash, :to) do def where_address_fields_match(query, address_hash, :to) do
where(query, [t], t.to_address_hash == ^address_hash) where(
query,
[t],
t.to_address_hash == ^address_hash or
(is_nil(t.to_address_hash) and t.created_contract_address_hash == ^address_hash)
)
end end
def where_address_fields_match(query, address_hash, :from) do def where_address_fields_match(query, address_hash, :from) do

@ -0,0 +1,97 @@
defmodule Explorer.Chain.StakingPool do
@moduledoc """
The representation of staking pool from POSDAO network.
Staking pools might be candidate or validator.
"""
use Ecto.Schema
import Ecto.Changeset
alias Explorer.Chain.{
Address,
Hash,
StakingPoolsDelegator,
Wei
}
@type t :: %__MODULE__{
staking_address_hash: Hash.Address.t(),
mining_address_hash: Hash.Address.t(),
banned_until: boolean,
delegators_count: integer,
is_active: boolean,
is_banned: boolean,
is_validator: boolean,
likelihood: integer,
staked_ratio: Decimal.t(),
self_staked_amount: Wei.t(),
staked_amount: Wei.t(),
was_banned_count: integer,
was_validator_count: integer,
is_deleted: boolean
}
@attrs ~w(
is_active delegators_count staked_amount self_staked_amount is_validator
was_validator_count is_banned was_banned_count banned_until likelihood
staked_ratio staking_address_hash mining_address_hash
)a
@req_attrs ~w(
is_active delegators_count staked_amount self_staked_amount is_validator
was_validator_count is_banned was_banned_count banned_until
staking_address_hash mining_address_hash
)a
schema "staking_pools" do
field(:banned_until, :integer)
field(:delegators_count, :integer)
field(:is_active, :boolean, default: false)
field(:is_banned, :boolean, default: false)
field(:is_validator, :boolean, default: false)
field(:likelihood, :decimal)
field(:staked_ratio, :decimal)
field(:self_staked_amount, Wei)
field(:staked_amount, Wei)
field(:was_banned_count, :integer)
field(:was_validator_count, :integer)
field(:is_deleted, :boolean, default: false)
has_many(:delegators, StakingPoolsDelegator, foreign_key: :pool_address_hash)
belongs_to(
:staking_address,
Address,
foreign_key: :staking_address_hash,
references: :hash,
type: Hash.Address
)
belongs_to(
:mining_address,
Address,
foreign_key: :mining_address_hash,
references: :hash,
type: Hash.Address
)
timestamps(null: false, type: :utc_datetime_usec)
end
@doc false
def changeset(staking_pool, attrs) do
staking_pool
|> cast(attrs, @attrs)
|> cast_assoc(:delegators)
|> validate_required(@req_attrs)
|> validate_staked_amount()
|> unique_constraint(:staking_address_hash)
end
defp validate_staked_amount(%{valid?: false} = c), do: c
defp validate_staked_amount(changeset) do
if get_field(changeset, :staked_amount) < get_field(changeset, :self_staked_amount) do
add_error(changeset, :staked_amount, "must be greater than self_staked_amount")
else
changeset
end
end
end

@ -0,0 +1,64 @@
defmodule Explorer.Chain.StakingPoolsDelegator do
@moduledoc """
The representation of delegators from POSDAO network.
Delegators make stakes on staking pools and withdraw from them.
"""
use Ecto.Schema
import Ecto.Changeset
alias Explorer.Chain.{
Address,
Hash,
StakingPool,
Wei
}
@type t :: %__MODULE__{
pool_address_hash: Hash.Address.t(),
delegator_address_hash: Hash.Address.t(),
max_ordered_withdraw_allowed: Wei.t(),
max_withdraw_allowed: Wei.t(),
ordered_withdraw: Wei.t(),
stake_amount: Wei.t(),
ordered_withdraw_epoch: integer()
}
@attrs ~w(
pool_address_hash delegator_address_hash max_ordered_withdraw_allowed
max_withdraw_allowed ordered_withdraw stake_amount ordered_withdraw_epoch
)a
schema "staking_pools_delegators" do
field(:max_ordered_withdraw_allowed, Wei)
field(:max_withdraw_allowed, Wei)
field(:ordered_withdraw, Wei)
field(:ordered_withdraw_epoch, :integer)
field(:stake_amount, Wei)
belongs_to(
:staking_pool,
StakingPool,
foreign_key: :pool_address_hash,
references: :staking_address_hash,
type: Hash.Address
)
belongs_to(
:delegator_address,
Address,
foreign_key: :delegator_address_hash,
references: :hash,
type: Hash.Address
)
timestamps(null: false, type: :utc_datetime_usec)
end
@doc false
def changeset(staking_pools_delegator, attrs) do
staking_pools_delegator
|> cast(attrs, @attrs)
|> validate_required(@attrs)
|> unique_constraint(:pool_address_hash, name: :pools_delegator_index)
end
end

@ -5,9 +5,9 @@ defmodule Explorer.Etherscan.Logs do
""" """
import Ecto.Query, only: [from: 2, where: 3, subquery: 1, order_by: 3, limit: 2] import Ecto.Query, only: [from: 2, where: 3, subquery: 1]
alias Explorer.Chain.Log alias Explorer.Chain.{Block, InternalTransaction, Log, Transaction}
alias Explorer.Repo alias Explorer.Repo
@base_filter %{ @base_filter %{
@ -67,33 +67,101 @@ defmodule Explorer.Etherscan.Logs do
""" """
@spec list_logs(map()) :: [map()] @spec list_logs(map()) :: [map()]
def list_logs(filter) do def list_logs(%{address_hash: address_hash} = filter) when not is_nil(address_hash) do
prepared_filter = Map.merge(@base_filter, filter) prepared_filter = Map.merge(@base_filter, filter)
query = logs_query = where_topic_match(Log, prepared_filter)
from(
l in Log, internal_transaction_log_query =
inner_join: t in assoc(l, :transaction), from(internal_transaction in InternalTransaction,
inner_join: b in assoc(t, :block), join: transaction in assoc(internal_transaction, :transaction),
where: b.number >= ^prepared_filter.from_block, join: log in ^logs_query,
where: b.number <= ^prepared_filter.to_block, on: log.transaction_hash == internal_transaction.transaction_hash,
where: internal_transaction.block_number >= ^prepared_filter.from_block,
where: internal_transaction.block_number <= ^prepared_filter.to_block,
where:
internal_transaction.to_address_hash == ^address_hash or
internal_transaction.from_address_hash == ^address_hash or
internal_transaction.created_contract_address_hash == ^address_hash,
select: select:
merge(map(l, ^@log_fields), %{ merge(map(log, ^@log_fields), %{
gas_price: t.gas_price, gas_price: transaction.gas_price,
gas_used: t.gas_used, gas_used: transaction.gas_used,
transaction_index: t.index, transaction_index: transaction.index,
block_number: b.number, block_number: transaction.block_number
block_timestamp: b.timestamp
}) })
) )
query all_transaction_logs_query =
|> subquery() from(transaction in Transaction,
|> where_address_match(prepared_filter) join: log in ^logs_query,
|> where_topic_match(prepared_filter) on: log.transaction_hash == transaction.hash,
|> order_by([l], l.block_number) where: transaction.block_number >= ^prepared_filter.from_block,
|> limit(1_000) where: transaction.block_number <= ^prepared_filter.to_block,
|> Repo.all() where:
transaction.to_address_hash == ^address_hash or
transaction.from_address_hash == ^address_hash or
transaction.created_contract_address_hash == ^address_hash,
select: map(log, ^@log_fields),
select_merge: map(transaction, [:gas_price, :gas_used, :block_number]),
select_merge: %{
transaction_index: transaction.index
},
union: ^internal_transaction_log_query
)
query_with_blocks =
from(log_transaction_data in subquery(all_transaction_logs_query),
join: block in Block,
on: block.number == log_transaction_data.block_number,
where: block.consensus == true,
where: log_transaction_data.address_hash == ^address_hash,
order_by: block.number,
limit: 1000,
select_merge: %{
block_timestamp: block.timestamp
}
)
Repo.all(query_with_blocks)
end
# Since address_hash was not present, we know that a
# topic filter has been applied, so we use a different
# query that is optimized for a logs filter over an
# address_hash
def list_logs(filter) do
prepared_filter = Map.merge(@base_filter, filter)
logs_query = where_topic_match(Log, prepared_filter)
block_transaction_query =
from(transaction in Transaction,
join: block in assoc(transaction, :block),
where: block.number >= ^prepared_filter.from_block,
where: block.number <= ^prepared_filter.to_block,
where: block.consensus == true,
select: %{
transaction_hash: transaction.hash,
gas_price: transaction.gas_price,
gas_used: transaction.gas_used,
transaction_index: transaction.index,
block_number: block.number,
block_timestamp: block.timestamp
}
)
query_with_block_transaction_data =
from(log in logs_query,
join: block_transaction_data in subquery(block_transaction_query),
on: block_transaction_data.transaction_hash == log.transaction_hash,
order_by: block_transaction_data.block_number,
limit: 1000,
select: block_transaction_data,
select_merge: map(log, ^@log_fields)
)
Repo.all(query_with_block_transaction_data)
end end
@topics [ @topics [
@ -112,12 +180,6 @@ defmodule Explorer.Etherscan.Logs do
topic2_3_opr: {:third_topic, :fourth_topic} topic2_3_opr: {:third_topic, :fourth_topic}
} }
defp where_address_match(query, %{address_hash: address_hash}) when not is_nil(address_hash) do
where(query, [l], l.address_hash == ^address_hash)
end
defp where_address_match(query, _), do: query
defp where_topic_match(query, filter) do defp where_topic_match(query, filter) do
case Enum.filter(@topics, &filter[&1]) do case Enum.filter(@topics, &filter[&1]) do
[] -> [] ->

@ -24,10 +24,11 @@ defmodule Explorer.Staking.PoolsReader do
@spec pool_data(String.t()) :: {:ok, map()} | :error @spec pool_data(String.t()) :: {:ok, map()} | :error
def pool_data(staking_address) do def pool_data(staking_address) do
with {:ok, [mining_address]} <- call_validators_method("miningByStakingAddress", [staking_address]), with {:ok, [mining_address]} <- call_validators_method("miningByStakingAddress", [staking_address]),
data = fetch_data(staking_address, mining_address), data = fetch_pool_data(staking_address, mining_address),
{:ok, [is_active]} <- data["isPoolActive"], {:ok, [is_active]} <- data["isPoolActive"],
{:ok, [delegator_addresses]} <- data["poolDelegators"], {:ok, [delegator_addresses]} <- data["poolDelegators"],
delegators_count = Enum.count(delegator_addresses), delegators_count = Enum.count(delegator_addresses),
delegators = delegators_data(delegator_addresses, staking_address),
{:ok, [staked_amount]} <- data["stakeAmountTotalMinusOrderedWithdraw"], {:ok, [staked_amount]} <- data["stakeAmountTotalMinusOrderedWithdraw"],
{:ok, [self_staked_amount]} <- data["stakeAmountMinusOrderedWithdraw"], {:ok, [self_staked_amount]} <- data["stakeAmountMinusOrderedWithdraw"],
{:ok, [is_validator]} <- data["isValidator"], {:ok, [is_validator]} <- data["isValidator"],
@ -38,8 +39,8 @@ defmodule Explorer.Staking.PoolsReader do
{ {
:ok, :ok,
%{ %{
staking_address: staking_address, staking_address_hash: staking_address,
mining_address: mining_address, mining_address_hash: mining_address,
is_active: is_active, is_active: is_active,
delegators_count: delegators_count, delegators_count: delegators_count,
staked_amount: staked_amount, staked_amount: staked_amount,
@ -48,7 +49,8 @@ defmodule Explorer.Staking.PoolsReader do
was_validator_count: was_validator_count, was_validator_count: was_validator_count,
is_banned: is_banned, is_banned: is_banned,
banned_until: banned_until, banned_until: banned_until,
was_banned_count: was_banned_count was_banned_count: was_banned_count,
delegators: delegators
} }
} }
else else
@ -57,6 +59,35 @@ defmodule Explorer.Staking.PoolsReader do
end end
end end
defp delegators_data(delegators, pool_address) do
Enum.map(delegators, fn address ->
data =
call_methods([
{:staking, "stakeAmount", [pool_address, address]},
{:staking, "orderedWithdrawAmount", [pool_address, address]},
{:staking, "maxWithdrawAllowed", [pool_address, address]},
{:staking, "maxWithdrawOrderAllowed", [pool_address, address]},
{:staking, "orderWithdrawEpoch", [pool_address, address]}
])
{:ok, [stake_amount]} = data["stakeAmount"]
{:ok, [ordered_withdraw]} = data["orderedWithdrawAmount"]
{:ok, [max_withdraw_allowed]} = data["maxWithdrawAllowed"]
{:ok, [max_ordered_withdraw_allowed]} = data["maxWithdrawOrderAllowed"]
{:ok, [ordered_withdraw_epoch]} = data["orderWithdrawEpoch"]
%{
delegator_address_hash: address,
pool_address_hash: pool_address,
stake_amount: stake_amount,
ordered_withdraw: ordered_withdraw,
max_withdraw_allowed: max_withdraw_allowed,
max_ordered_withdraw_allowed: max_ordered_withdraw_allowed,
ordered_withdraw_epoch: ordered_withdraw_epoch
}
end)
end
defp call_staking_method(method, params) do defp call_staking_method(method, params) do
%{^method => resp} = %{^method => resp} =
Reader.query_contract(config(:staking_contract_address), abi("staking.json"), %{ Reader.query_contract(config(:staking_contract_address), abi("staking.json"), %{
@ -75,10 +106,8 @@ defmodule Explorer.Staking.PoolsReader do
resp resp
end end
defp fetch_data(staking_address, mining_address) do defp fetch_pool_data(staking_address, mining_address) do
contract_abi = abi("staking.json") ++ abi("validators.json") call_methods([
methods = [
{:staking, "isPoolActive", [staking_address]}, {:staking, "isPoolActive", [staking_address]},
{:staking, "poolDelegators", [staking_address]}, {:staking, "poolDelegators", [staking_address]},
{:staking, "stakeAmountTotalMinusOrderedWithdraw", [staking_address]}, {:staking, "stakeAmountTotalMinusOrderedWithdraw", [staking_address]},
@ -88,7 +117,11 @@ defmodule Explorer.Staking.PoolsReader do
{:validators, "isValidatorBanned", [mining_address]}, {:validators, "isValidatorBanned", [mining_address]},
{:validators, "bannedUntil", [mining_address]}, {:validators, "bannedUntil", [mining_address]},
{:validators, "banCounter", [mining_address]} {:validators, "banCounter", [mining_address]}
] ])
end
defp call_methods(methods) do
contract_abi = abi("staking.json") ++ abi("validators.json")
methods methods
|> Enum.map(&format_request/1) |> Enum.map(&format_request/1)

@ -22,6 +22,16 @@ defmodule Explorer.Token.MetadataRetriever do
"payable" => false, "payable" => false,
"type" => "function" "type" => "function"
}, },
%{
"constant" => true,
"inputs" => [],
"name" => "name",
"outputs" => [
%{"name" => "", "type" => "bytes32"}
],
"payable" => false,
"type" => "function"
},
%{ %{
"constant" => true, "constant" => true,
"inputs" => [], "inputs" => [],
@ -60,6 +70,19 @@ defmodule Explorer.Token.MetadataRetriever do
], ],
"payable" => false, "payable" => false,
"type" => "function" "type" => "function"
},
%{
"constant" => true,
"inputs" => [],
"name" => "symbol",
"outputs" => [
%{
"name" => "",
"type" => "bytes32"
}
],
"payable" => false,
"type" => "function"
} }
] ]

@ -0,0 +1,27 @@
defmodule Explorer.Repo.Migrations.CreateStakingPools do
use Ecto.Migration
def change do
create table(:staking_pools) do
add(:is_active, :boolean, default: false, null: false)
add(:is_deleted, :boolean, default: false, null: false)
add(:delegators_count, :integer)
add(:staked_amount, :numeric, precision: 100)
add(:self_staked_amount, :numeric, precision: 100)
add(:is_validator, :boolean, default: false, null: false)
add(:was_validator_count, :integer)
add(:is_banned, :boolean, default: false, null: false)
add(:was_banned_count, :integer)
add(:banned_until, :bigint)
add(:likelihood, :decimal, precision: 5, scale: 2)
add(:staked_ratio, :decimal, precision: 5, scale: 2)
add(:staking_address_hash, :bytea)
add(:mining_address_hash, :bytea)
timestamps(null: false, type: :utc_datetime_usec)
end
create(index(:staking_pools, [:staking_address_hash], unique: true))
create(index(:staking_pools, [:mining_address_hash]))
end
end

@ -0,0 +1,26 @@
defmodule Explorer.Repo.Migrations.CreateStakingPoolsDelegator do
use Ecto.Migration
def change do
create table(:staking_pools_delegators) do
add(:delegator_address_hash, :bytea)
add(:pool_address_hash, :bytea)
add(:stake_amount, :numeric, precision: 100)
add(:ordered_withdraw, :numeric, precision: 100)
add(:max_withdraw_allowed, :numeric, precision: 100)
add(:max_ordered_withdraw_allowed, :numeric, precision: 100)
add(:ordered_withdraw_epoch, :integer)
timestamps(null: false, type: :utc_datetime_usec)
end
create(index(:staking_pools_delegators, [:delegator_address_hash]))
create(
index(:staking_pools_delegators, [:delegator_address_hash, :pool_address_hash],
unique: true,
name: :pools_delegator_index
)
)
end
end

@ -0,0 +1,32 @@
defmodule Explorer.Chain.Import.Runner.StakingPoolsDelegatorsTest do
use Explorer.DataCase
import Explorer.Factory
alias Ecto.Multi
alias Explorer.Chain.Import.Runner.StakingPoolsDelegators
alias Explorer.Chain.StakingPoolsDelegator
describe "run/1" do
test "insert new pools list" do
delegators =
[params_for(:staking_pools_delegator), params_for(:staking_pools_delegator)]
|> Enum.map(fn param ->
changeset = StakingPoolsDelegator.changeset(%StakingPoolsDelegator{}, param)
changeset.changes
end)
assert {:ok, %{insert_staking_pools_delegators: list}} = run_changes(delegators)
assert Enum.count(list) == Enum.count(delegators)
end
end
defp run_changes(changes) do
Multi.new()
|> StakingPoolsDelegators.run(changes, %{
timeout: :infinity,
timestamps: %{inserted_at: DateTime.utc_now(), updated_at: DateTime.utc_now()}
})
|> Repo.transaction()
end
end

@ -5,25 +5,30 @@ defmodule Explorer.Chain.Import.Runner.StakingPoolsTest do
alias Ecto.Multi alias Ecto.Multi
alias Explorer.Chain.Import.Runner.StakingPools alias Explorer.Chain.Import.Runner.StakingPools
alias Explorer.Chain.StakingPool
describe "run/1" do describe "run/1" do
test "insert new pools list" do test "insert new pools list" do
pools = [pool1, pool2, pool3, pool4] = build_list(4, :staking_pool) pools =
[pool1, pool2] =
[params_for(:staking_pool), params_for(:staking_pool)]
|> Enum.map(fn param ->
changeset = StakingPool.changeset(%StakingPool{}, param)
changeset.changes
end)
assert {:ok, %{insert_staking_pools: list}} = run_changes(pools) assert {:ok, %{insert_staking_pools: list}} = run_changes(pools)
assert Enum.count(list) == Enum.count(pools) assert Enum.count(list) == Enum.count(pools)
saved_list = saved_list =
Explorer.Chain.Address.Name Explorer.Chain.StakingPool
|> Repo.all() |> Repo.all()
|> Enum.reduce(%{}, fn pool, acc -> |> Enum.reduce(%{}, fn pool, acc ->
Map.put(acc, pool.address_hash, pool) Map.put(acc, pool.staking_address_hash, pool)
end) end)
assert saved_list[pool1.address_hash].metadata["staked_ratio"] == 0.25 assert saved_list[pool1.staking_address_hash].staked_ratio == Decimal.new("50.00")
assert saved_list[pool2.address_hash].metadata["staked_ratio"] == 0.25 assert saved_list[pool2.staking_address_hash].staked_ratio == Decimal.new("50.00")
assert saved_list[pool3.address_hash].metadata["staked_ratio"] == 0.25
assert saved_list[pool4.address_hash].metadata["staked_ratio"] == 0.25
end end
end end

@ -0,0 +1,18 @@
defmodule Explorer.Chain.StakingPoolTest do
use Explorer.DataCase
alias Explorer.Chain.StakingPool
describe "changeset/2" do
test "with valid attributes" do
params = params_for(:staking_pool)
changeset = StakingPool.changeset(%StakingPool{}, params)
assert changeset.valid?
end
test "with invalid attributes" do
changeset = StakingPool.changeset(%StakingPool{}, %{staking_address_hash: 0})
refute changeset.valid?
end
end
end

@ -0,0 +1,18 @@
defmodule Explorer.Chain.StakingPoolsDelegatorTest do
use Explorer.DataCase
alias Explorer.Chain.StakingPoolsDelegator
describe "changeset/2" do
test "with valid attributes" do
params = params_for(:staking_pools_delegator)
changeset = StakingPoolsDelegator.changeset(%StakingPoolsDelegator{}, params)
assert changeset.valid?
end
test "with invalid attributes" do
changeset = StakingPoolsDelegator.changeset(%StakingPoolsDelegator{}, %{pool_address_hash: 0})
refute changeset.valid?
end
end
end

@ -93,6 +93,50 @@ defmodule Explorer.ChainTest do
assert Enum.count(Chain.address_to_logs(address, paging_options: paging_options2)) == 50 assert Enum.count(Chain.address_to_logs(address, paging_options: paging_options2)) == 50
end end
test "searches logs by topic when the first topic matches" do
address = insert(:address)
transaction1 =
:transaction
|> insert(to_address: address)
|> with_block()
insert(:log, transaction: transaction1, index: 1, address: address)
transaction2 =
:transaction
|> insert(from_address: address)
|> with_block()
insert(:log, transaction: transaction2, index: 2, address: address, first_topic: "test")
[found_log] = Chain.address_to_logs(address, topic: "test")
assert found_log.transaction.hash == transaction2.hash
end
test "searches logs by topic when the fourth topic matches" do
address = insert(:address)
transaction1 =
:transaction
|> insert(to_address: address)
|> with_block()
insert(:log, transaction: transaction1, index: 1, address: address, fourth_topic: "test")
transaction2 =
:transaction
|> insert(from_address: address)
|> with_block()
insert(:log, transaction: transaction2, index: 2, address: address)
[found_log] = Chain.address_to_logs(address, topic: "test")
assert found_log.transaction.hash == transaction1.hash
end
end end
describe "address_to_transactions_with_rewards/2" do describe "address_to_transactions_with_rewards/2" do
@ -3957,54 +4001,54 @@ defmodule Explorer.ChainTest do
describe "staking_pools/3" do describe "staking_pools/3" do
test "validators staking pools" do test "validators staking pools" do
inserted_validator = insert(:address_name, primary: true, metadata: %{is_active: true, is_validator: true}) inserted_validator = insert(:staking_pool, is_active: true, is_validator: true)
insert(:address_name, primary: true, metadata: %{is_active: true, is_validator: false}) insert(:staking_pool, is_active: true, is_validator: false)
options = %PagingOptions{page_size: 20, page_number: 1} options = %PagingOptions{page_size: 20, page_number: 1}
assert [gotten_validator] = Chain.staking_pools(:validator, options) assert [gotten_validator] = Chain.staking_pools(:validator, options)
assert inserted_validator.address_hash == gotten_validator.address_hash assert inserted_validator.staking_address_hash == gotten_validator.staking_address_hash
end end
test "active staking pools" do test "active staking pools" do
inserted_validator = insert(:address_name, primary: true, metadata: %{is_active: true}) inserted_pool = insert(:staking_pool, is_active: true)
insert(:address_name, primary: true, metadata: %{is_active: false}) insert(:staking_pool, is_active: false)
options = %PagingOptions{page_size: 20, page_number: 1} options = %PagingOptions{page_size: 20, page_number: 1}
assert [gotten_validator] = Chain.staking_pools(:active, options) assert [gotten_pool] = Chain.staking_pools(:active, options)
assert inserted_validator.address_hash == gotten_validator.address_hash assert inserted_pool.staking_address_hash == gotten_pool.staking_address_hash
end end
test "inactive staking pools" do test "inactive staking pools" do
insert(:address_name, primary: true, metadata: %{is_active: true}) insert(:staking_pool, is_active: true)
inserted_validator = insert(:address_name, primary: true, metadata: %{is_active: false}) inserted_pool = insert(:staking_pool, is_active: false)
options = %PagingOptions{page_size: 20, page_number: 1} options = %PagingOptions{page_size: 20, page_number: 1}
assert [gotten_validator] = Chain.staking_pools(:inactive, options) assert [gotten_pool] = Chain.staking_pools(:inactive, options)
assert inserted_validator.address_hash == gotten_validator.address_hash assert inserted_pool.staking_address_hash == gotten_pool.staking_address_hash
end end
end end
describe "staking_pools_count/1" do describe "staking_pools_count/1" do
test "validators staking pools" do test "validators staking pools" do
insert(:address_name, primary: true, metadata: %{is_active: true, is_validator: true}) insert(:staking_pool, is_active: true, is_validator: true)
insert(:address_name, primary: true, metadata: %{is_active: true, is_validator: false}) insert(:staking_pool, is_active: true, is_validator: false)
assert Chain.staking_pools_count(:validator) == 1 assert Chain.staking_pools_count(:validator) == 1
end end
test "active staking pools" do test "active staking pools" do
insert(:address_name, primary: true, metadata: %{is_active: true}) insert(:staking_pool, is_active: true)
insert(:address_name, primary: true, metadata: %{is_active: false}) insert(:staking_pool, is_active: false)
assert Chain.staking_pools_count(:active) == 1 assert Chain.staking_pools_count(:active) == 1
end end
test "inactive staking pools" do test "inactive staking pools" do
insert(:address_name, primary: true, metadata: %{is_active: true}) insert(:staking_pool, is_active: true)
insert(:address_name, primary: true, metadata: %{is_active: false}) insert(:staking_pool, is_active: false)
assert Chain.staking_pools_count(:inactive) == 1 assert Chain.staking_pools_count(:inactive) == 1
end end

@ -24,7 +24,7 @@ defmodule Explorer.Etherscan.LogsTest do
%Transaction{block: block} = %Transaction{block: block} =
:transaction :transaction
|> insert() |> insert(to_address: contract_address)
|> with_block() |> with_block()
filter = %{ filter = %{
@ -42,7 +42,7 @@ defmodule Explorer.Etherscan.LogsTest do
transaction = transaction =
%Transaction{block: block} = %Transaction{block: block} =
:transaction :transaction
|> insert() |> insert(to_address: contract_address)
|> with_block() |> with_block()
log = insert(:log, address: contract_address, transaction: transaction) log = insert(:log, address: contract_address, transaction: transaction)
@ -76,7 +76,7 @@ defmodule Explorer.Etherscan.LogsTest do
transaction = transaction =
%Transaction{block: block} = %Transaction{block: block} =
:transaction :transaction
|> insert() |> insert(to_address: contract_address)
|> with_block() |> with_block()
insert_list(2, :log, address: contract_address, transaction: transaction) insert_list(2, :log, address: contract_address, transaction: transaction)
@ -101,13 +101,13 @@ defmodule Explorer.Etherscan.LogsTest do
transaction_block1 = transaction_block1 =
%Transaction{} = %Transaction{} =
:transaction :transaction
|> insert() |> insert(to_address: contract_address)
|> with_block(first_block) |> with_block(first_block)
transaction_block2 = transaction_block2 =
%Transaction{} = %Transaction{} =
:transaction :transaction
|> insert() |> insert(to_address: contract_address)
|> with_block(second_block) |> with_block(second_block)
insert(:log, address: contract_address, transaction: transaction_block1) insert(:log, address: contract_address, transaction: transaction_block1)
@ -134,13 +134,13 @@ defmodule Explorer.Etherscan.LogsTest do
transaction_block1 = transaction_block1 =
%Transaction{} = %Transaction{} =
:transaction :transaction
|> insert() |> insert(to_address: contract_address)
|> with_block(first_block) |> with_block(first_block)
transaction_block2 = transaction_block2 =
%Transaction{} = %Transaction{} =
:transaction :transaction
|> insert() |> insert(to_address: contract_address)
|> with_block(second_block) |> with_block(second_block)
insert(:log, address: contract_address, transaction: transaction_block1) insert(:log, address: contract_address, transaction: transaction_block1)
@ -164,7 +164,7 @@ defmodule Explorer.Etherscan.LogsTest do
transaction = transaction =
%Transaction{block: block} = %Transaction{block: block} =
:transaction :transaction
|> insert() |> insert(to_address: contract_address)
|> with_block() |> with_block()
log1_details = [ log1_details = [
@ -200,7 +200,7 @@ defmodule Explorer.Etherscan.LogsTest do
transaction = transaction =
%Transaction{block: block} = %Transaction{block: block} =
:transaction :transaction
|> insert() |> insert(to_address: contract_address)
|> with_block() |> with_block()
log1_details = [ log1_details = [
@ -241,7 +241,7 @@ defmodule Explorer.Etherscan.LogsTest do
transaction = transaction =
%Transaction{block: block} = %Transaction{block: block} =
:transaction :transaction
|> insert() |> insert(to_address: contract_address)
|> with_block() |> with_block()
log1_details = [ log1_details = [
@ -280,7 +280,7 @@ defmodule Explorer.Etherscan.LogsTest do
transaction = transaction =
%Transaction{block: block} = %Transaction{block: block} =
:transaction :transaction
|> insert() |> insert(to_address: contract_address)
|> with_block() |> with_block()
log1_details = [ log1_details = [
@ -317,7 +317,7 @@ defmodule Explorer.Etherscan.LogsTest do
transaction = transaction =
%Transaction{block: block} = %Transaction{block: block} =
:transaction :transaction
|> insert() |> insert(to_address: contract_address)
|> with_block() |> with_block()
log1_details = [ log1_details = [
@ -358,7 +358,7 @@ defmodule Explorer.Etherscan.LogsTest do
transaction = transaction =
%Transaction{block: block} = %Transaction{block: block} =
:transaction :transaction
|> insert() |> insert(to_address: contract_address)
|> with_block() |> with_block()
log1_details = [ log1_details = [
@ -415,7 +415,7 @@ defmodule Explorer.Etherscan.LogsTest do
transaction = transaction =
%Transaction{block: block} = %Transaction{block: block} =
:transaction :transaction
|> insert() |> insert(to_address: contract_address)
|> with_block() |> with_block()
log1_details = [ log1_details = [
@ -472,7 +472,7 @@ defmodule Explorer.Etherscan.LogsTest do
transaction = transaction =
%Transaction{block: block} = %Transaction{block: block} =
:transaction :transaction
|> insert() |> insert(to_address: contract_address)
|> with_block() |> with_block()
log1_details = [ log1_details = [
@ -529,7 +529,7 @@ defmodule Explorer.Etherscan.LogsTest do
transaction = transaction =
%Transaction{block: block} = %Transaction{block: block} =
:transaction :transaction
|> insert() |> insert(to_address: contract_address)
|> with_block() |> with_block()
log1_details = [ log1_details = [
@ -595,19 +595,19 @@ defmodule Explorer.Etherscan.LogsTest do
transaction_block1 = transaction_block1 =
%Transaction{} = %Transaction{} =
:transaction :transaction
|> insert() |> insert(to_address: contract_address)
|> with_block(first_block) |> with_block(first_block)
transaction_block2 = transaction_block2 =
%Transaction{} = %Transaction{} =
:transaction :transaction
|> insert() |> insert(to_address: contract_address)
|> with_block(second_block) |> with_block(second_block)
transaction_block3 = transaction_block3 =
%Transaction{} = %Transaction{} =
:transaction :transaction
|> insert() |> insert(to_address: contract_address)
|> with_block(third_block) |> with_block(third_block)
insert(:log, address: contract_address, transaction: transaction_block3) insert(:log, address: contract_address, transaction: transaction_block3)

@ -30,25 +30,38 @@ defmodule Explorer.Token.PoolsReaderTest do
test "get_pool_data success" do test "get_pool_data success" do
get_pool_data_from_blockchain() get_pool_data_from_blockchain()
address = <<11, 47, 94, 47, 60, 189, 134, 78, 170, 44, 100, 46, 55, 105, 193, 88, 35, 97, 202, 246>> address = <<219, 156, 178, 71, 141, 145, 119, 25, 197, 56, 98, 0, 134, 114, 22, 104, 8, 37, 133, 119>>
response = { response =
:ok, {:ok,
%{ %{
banned_until: 0, banned_until: 0,
delegators_count: 0, is_active: true,
is_active: true, is_banned: false,
is_banned: false, is_validator: true,
is_validator: true, was_banned_count: 0,
mining_address: was_validator_count: 2,
<<187, 202, 168, 212, 130, 137, 187, 31, 252, 249, 128, 141, 154, 164, 177, 210, 21, 5, 76, 120>>, delegators: [
staked_amount: 0, %{
self_staked_amount: 0, delegator_address_hash:
staking_address: <<11, 47, 94, 47, 60, 189, 134, 78, 170, 44, 100, 46, 55, 105, 193, 88, 35, 97, 202, 246>>, <<243, 231, 124, 74, 245, 235, 47, 51, 175, 255, 118, 25, 216, 209, 231, 81, 215, 24, 164, 145>>,
was_banned_count: 0, max_ordered_withdraw_allowed: 1_000_000_000_000_000_000,
was_validator_count: 2 max_withdraw_allowed: 1_000_000_000_000_000_000,
} ordered_withdraw: 0,
} ordered_withdraw_epoch: 0,
pool_address_hash:
<<219, 156, 178, 71, 141, 145, 119, 25, 197, 56, 98, 0, 134, 114, 22, 104, 8, 37, 133, 119>>,
stake_amount: 1_000_000_000_000_000_000
}
],
delegators_count: 1,
mining_address_hash:
<<190, 105, 235, 9, 104, 34, 106, 24, 8, 151, 94, 26, 31, 33, 39, 102, 127, 43, 255, 179>>,
self_staked_amount: 2_000_000_000_000_000_000,
staked_amount: 3_000_000_000_000_000_000,
staking_address_hash:
<<219, 156, 178, 71, 141, 145, 119, 25, 197, 56, 98, 0, 134, 114, 22, 104, 8, 37, 133, 119>>
}}
assert PoolsReader.pool_data(address) == response assert PoolsReader.pool_data(address) == response
end end
@ -101,7 +114,7 @@ defmodule Explorer.Token.PoolsReaderTest do
expect( expect(
EthereumJSONRPC.Mox, EthereumJSONRPC.Mox,
:json_rpc, :json_rpc,
2, 3,
fn requests, _opts -> fn requests, _opts ->
{:ok, {:ok,
Enum.map(requests, fn Enum.map(requests, fn
@ -110,13 +123,13 @@ defmodule Explorer.Token.PoolsReaderTest do
id: id, id: id,
method: "eth_call", method: "eth_call",
params: [ params: [
%{data: "0x005351750000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _}, %{data: "0x00535175000000000000000000000000db9cb2478d917719c53862008672166808258577", to: _},
"latest" "latest"
] ]
} -> } ->
%{ %{
id: id, id: id,
result: "0x000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78" result: "0x000000000000000000000000be69eb0968226a1808975e1a1f2127667f2bffb3"
} }
# isPoolActive # isPoolActive
@ -124,7 +137,7 @@ defmodule Explorer.Token.PoolsReaderTest do
id: id, id: id,
method: "eth_call", method: "eth_call",
params: [ params: [
%{data: "0xa711e6a10000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _}, %{data: "0xa711e6a1000000000000000000000000db9cb2478d917719c53862008672166808258577", to: _},
"latest" "latest"
] ]
} -> } ->
@ -138,14 +151,14 @@ defmodule Explorer.Token.PoolsReaderTest do
id: id, id: id,
method: "eth_call", method: "eth_call",
params: [ params: [
%{data: "0x9ea8082b0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _}, %{data: "0x9ea8082b000000000000000000000000db9cb2478d917719c53862008672166808258577", to: _},
"latest" "latest"
] ]
} -> } ->
%{ %{
id: id, id: id,
result: result:
"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000" "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000f3e77c4af5eb2f33afff7619d8d1e751d718a491"
} }
# stakeAmountTotalMinusOrderedWithdraw # stakeAmountTotalMinusOrderedWithdraw
@ -153,13 +166,13 @@ defmodule Explorer.Token.PoolsReaderTest do
id: id, id: id,
method: "eth_call", method: "eth_call",
params: [ params: [
%{data: "0x234fbf2b0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _}, %{data: "0x234fbf2b000000000000000000000000db9cb2478d917719c53862008672166808258577", to: _},
"latest" "latest"
] ]
} -> } ->
%{ %{
id: id, id: id,
result: "0x0000000000000000000000000000000000000000000000000000000000000000" result: "0x00000000000000000000000000000000000000000000000029a2241af62c0000"
} }
# stakeAmountMinusOrderedWithdraw # stakeAmountMinusOrderedWithdraw
@ -170,7 +183,7 @@ defmodule Explorer.Token.PoolsReaderTest do
params: [ params: [
%{ %{
data: data:
"0x58daab6a0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf60000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", "0x58daab6a000000000000000000000000db9cb2478d917719c53862008672166808258577000000000000000000000000db9cb2478d917719c53862008672166808258577",
to: _ to: _
}, },
"latest" "latest"
@ -178,7 +191,7 @@ defmodule Explorer.Token.PoolsReaderTest do
} -> } ->
%{ %{
id: id, id: id,
result: "0x0000000000000000000000000000000000000000000000000000000000000000" result: "0x0000000000000000000000000000000000000000000000001bc16d674ec80000"
} }
# isValidator # isValidator
@ -186,7 +199,7 @@ defmodule Explorer.Token.PoolsReaderTest do
id: id, id: id,
method: "eth_call", method: "eth_call",
params: [ params: [
%{data: "0xfacd743b000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _}, %{data: "0xfacd743b000000000000000000000000be69eb0968226a1808975e1a1f2127667f2bffb3", to: _},
"latest" "latest"
] ]
} -> } ->
@ -200,7 +213,7 @@ defmodule Explorer.Token.PoolsReaderTest do
id: id, id: id,
method: "eth_call", method: "eth_call",
params: [ params: [
%{data: "0xb41832e4000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _}, %{data: "0xb41832e4000000000000000000000000be69eb0968226a1808975e1a1f2127667f2bffb3", to: _},
"latest" "latest"
] ]
} -> } ->
@ -214,7 +227,7 @@ defmodule Explorer.Token.PoolsReaderTest do
id: id, id: id,
method: "eth_call", method: "eth_call",
params: [ params: [
%{data: "0xa92252ae000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _}, %{data: "0xa92252ae000000000000000000000000be69eb0968226a1808975e1a1f2127667f2bffb3", to: _},
"latest" "latest"
] ]
} -> } ->
@ -228,7 +241,7 @@ defmodule Explorer.Token.PoolsReaderTest do
id: id, id: id,
method: "eth_call", method: "eth_call",
params: [ params: [
%{data: "0x5836d08a000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _}, %{data: "0x5836d08a000000000000000000000000be69eb0968226a1808975e1a1f2127667f2bffb3", to: _},
"latest" "latest"
] ]
} -> } ->
@ -242,7 +255,98 @@ defmodule Explorer.Token.PoolsReaderTest do
id: id, id: id,
method: "eth_call", method: "eth_call",
params: [ params: [
%{data: "0x1d0cd4c6000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _}, %{data: "0x1d0cd4c6000000000000000000000000be69eb0968226a1808975e1a1f2127667f2bffb3", to: _},
"latest"
]
} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000000000000000000"
}
# DELEGATOR
# stakeAmount
%{
id: id,
method: "eth_call",
params: [
%{
data:
"0xa697ecff000000000000000000000000db9cb2478d917719c53862008672166808258577000000000000000000000000f3e77c4af5eb2f33afff7619d8d1e751d718a491",
to: _
},
"latest"
]
} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000"
}
# orderedWithdrawAmount
%{
id: id,
method: "eth_call",
params: [
%{
data:
"0xe9ab0300000000000000000000000000db9cb2478d917719c53862008672166808258577000000000000000000000000f3e77c4af5eb2f33afff7619d8d1e751d718a491",
to: _
},
"latest"
]
} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000000000000000000"
}
# maxWithdrawAllowed
%{
id: id,
method: "eth_call",
params: [
%{
data:
"0x6bda1577000000000000000000000000db9cb2478d917719c53862008672166808258577000000000000000000000000f3e77c4af5eb2f33afff7619d8d1e751d718a491",
to: _
},
"latest"
]
} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000"
}
# maxWithdrawOrderAllowed
%{
id: id,
method: "eth_call",
params: [
%{
data:
"0x950a6513000000000000000000000000db9cb2478d917719c53862008672166808258577000000000000000000000000f3e77c4af5eb2f33afff7619d8d1e751d718a491",
to: _
},
"latest"
]
} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000"
}
# orderWithdrawEpoch
%{
id: id,
method: "eth_call",
params: [
%{
data:
"0xa4205967000000000000000000000000db9cb2478d917719c53862008672166808258577000000000000000000000000f3e77c4af5eb2f33afff7619d8d1e751d718a491",
to: _
},
"latest" "latest"
] ]
} -> } ->

@ -426,4 +426,51 @@ defmodule Explorer.Token.MetadataRetrieverTest do
on_exit(fn -> Application.put_env(:explorer, :token_functions_reader_max_retries, original) end) on_exit(fn -> Application.put_env(:explorer, :token_functions_reader_max_retries, original) end)
end end
end end
test "returns name and symbol when they are bytes32" do
token = insert(:token, contract_address: build(:contract_address))
expect(
EthereumJSONRPC.Mox,
:json_rpc,
1,
fn requests, _opts ->
{:ok,
Enum.map(requests, fn
%{id: id, method: "eth_call", params: [%{data: "0x313ce567", to: _}, "latest"]} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000000000000000012"
}
%{id: id, method: "eth_call", params: [%{data: "0x06fdde03", to: _}, "latest"]} ->
%{
id: id,
result: "0x4d616b6572000000000000000000000000000000000000000000000000000000"
}
%{id: id, method: "eth_call", params: [%{data: "0x95d89b41", to: _}, "latest"]} ->
%{
id: id,
result: "0x4d4b520000000000000000000000000000000000000000000000000000000000"
}
%{id: id, method: "eth_call", params: [%{data: "0x18160ddd", to: _}, "latest"]} ->
%{
id: id,
result: "0x00000000000000000000000000000000000000000000d3c21bcecceda1000000"
}
end)}
end
)
expected = %{
decimals: 18,
name: "Maker",
symbol: "MKR",
total_supply: 1_000_000_000_000_000_000_000_000
}
assert MetadataRetriever.get_functions_of(token.contract_address_hash) == expected
end
end end

@ -26,7 +26,9 @@ defmodule Explorer.Factory do
SmartContract, SmartContract,
Token, Token,
TokenTransfer, TokenTransfer,
Transaction Transaction,
StakingPool,
StakingPoolsDelegator
} }
alias Explorer.Market.MarketHistory alias Explorer.Market.MarketHistory
@ -612,22 +614,34 @@ defmodule Explorer.Factory do
end end
def staking_pool_factory do def staking_pool_factory do
%{ wei_per_ether = 1_000_000_000_000_000_000
address_hash: address_hash(),
metadata: %{ %StakingPool{
banned_unitil: 0, staking_address_hash: address_hash(),
delegators_count: 0, mining_address_hash: address_hash(),
is_active: true, banned_until: 0,
is_banned: false, delegators_count: 0,
is_validator: true, is_active: true,
mining_address: address_hash(), is_banned: false,
retries_count: 1, is_validator: true,
staked_amount: 25, staked_amount: wei_per_ether * 500,
was_banned_count: 0, self_staked_amount: wei_per_ether * 300,
was_validator_count: 1 was_banned_count: 0,
}, was_validator_count: 1
name: "anonymous", }
primary: true end
def staking_pools_delegator_factory do
wei_per_ether = 1_000_000_000_000_000_000
%StakingPoolsDelegator{
pool_address_hash: address_hash(),
delegator_address_hash: address_hash(),
max_ordered_withdraw_allowed: wei_per_ether * 100,
max_withdraw_allowed: wei_per_ether * 50,
ordered_withdraw: wei_per_ether * 600,
stake_amount: wei_per_ether * 200,
ordered_withdraw_epoch: 2
} }
end end
end end

@ -9,6 +9,7 @@ defmodule Indexer.Fetcher.StakingPools do
require Logger require Logger
alias Explorer.Chain alias Explorer.Chain
alias Explorer.Chain.StakingPool
alias Explorer.Staking.PoolsReader alias Explorer.Staking.PoolsReader
alias Indexer.BufferedTask alias Indexer.BufferedTask
alias Indexer.Fetcher.StakingPools.Supervisor, as: StakingPoolsSupervisor alias Indexer.Fetcher.StakingPools.Supervisor, as: StakingPoolsSupervisor
@ -71,7 +72,7 @@ defmodule Indexer.Fetcher.StakingPools do
def entry(pool_address) do def entry(pool_address) do
%{ %{
staking_address: pool_address, staking_address_hash: pool_address,
retries_count: 0 retries_count: 0
} }
end end
@ -79,7 +80,7 @@ defmodule Indexer.Fetcher.StakingPools do
defp fetch_from_blockchain(addresses) do defp fetch_from_blockchain(addresses) do
addresses addresses
|> Enum.filter(&(&1.retries_count <= @max_retries)) |> Enum.filter(&(&1.retries_count <= @max_retries))
|> Enum.map(fn %{staking_address: staking_address} = pool -> |> Enum.map(fn %{staking_address_hash: staking_address} = pool ->
case PoolsReader.pool_data(staking_address) do case PoolsReader.pool_data(staking_address) do
{:ok, data} -> {:ok, data} ->
Map.merge(pool, data) Map.merge(pool, data)
@ -93,15 +94,22 @@ defmodule Indexer.Fetcher.StakingPools do
defp import_pools(pools) do defp import_pools(pools) do
{failed, success} = {failed, success} =
Enum.reduce(pools, {[], []}, fn Enum.reduce(pools, {[], []}, fn
%{error: _error, staking_address: address}, {failed, success} -> %{error: _error} = pool, {failed, success} ->
{[address | failed], success} {[pool | failed], success}
pool, {failed, success} -> pool, {failed, success} ->
{failed, [changeset(pool) | success]} changeset = StakingPool.changeset(%StakingPool{}, pool)
if changeset.valid? do
{failed, [changeset.changes | success]}
else
{[pool | failed], success}
end
end) end)
import_params = %{ import_params = %{
staking_pools: %{params: success}, staking_pools: %{params: remove_assoc(success)},
staking_pools_delegators: %{params: delegators_list(success)},
timeout: :infinity timeout: :infinity
} }
@ -118,20 +126,15 @@ defmodule Indexer.Fetcher.StakingPools do
failed failed
end end
defp changeset(%{staking_address: staking_address} = pool) do defp delegators_list(pools) do
{:ok, mining_address} = Chain.Hash.Address.cast(pool[:mining_address]) Enum.reduce(pools, [], fn pool, acc ->
pool.delegators
data = |> Enum.map(&Map.get(&1, :changes))
pool |> Enum.concat(acc)
|> Map.delete(:staking_address) end)
|> Map.put(:mining_address, mining_address) end
|> Map.put(:is_pool, true)
%{ defp remove_assoc(pools) do
name: "anonymous", Enum.map(pools, &Map.delete(&1, :delegators))
primary: true,
address_hash: staking_address,
metadata: data
}
end end
end end

@ -6,7 +6,7 @@ defmodule Indexer.Fetcher.StakingPoolsTest do
alias Indexer.Fetcher.StakingPools alias Indexer.Fetcher.StakingPools
alias Explorer.Staking.PoolsReader alias Explorer.Staking.PoolsReader
alias Explorer.Chain.Address alias Explorer.Chain.StakingPool
@moduletag :capture_log @moduletag :capture_log
@ -33,15 +33,15 @@ defmodule Indexer.Fetcher.StakingPoolsTest do
success_address = success_address =
list list
|> List.first() |> List.first()
|> Map.get(:staking_address) |> Map.get(:staking_address_hash)
get_pool_data_from_blockchain() get_pool_data_from_blockchain()
assert {:retry, retry_list} = StakingPools.run(list, nil) assert {:retry, retry_list} = StakingPools.run(list, nil)
assert Enum.count(retry_list) == 2 assert Enum.count(retry_list) == 2
pool = Explorer.Repo.get_by(Address.Name, address_hash: success_address) pool = Explorer.Repo.get_by(StakingPool, staking_address_hash: success_address)
assert pool.name == "anonymous" assert pool.is_active == true
end end
end end

@ -0,0 +1,323 @@
<p align="center">
<a href="https://blockscout.com">
<img width="200" src="https://blockscout.com/eth/mainnet/android-chrome-192x192.png" \>
</a>
</p>
<h1 align="center">BlockScout</h1>
<p align="center">Blockchain Explorer for inspecting and analyzing EVM Chains.</p>
<div align="center">
[![CircleCI](https://circleci.com/gh/poanetwork/blockscout.svg?style=svg&circle-token=f8823a3d0090407c11f87028c73015a331dbf604)](https://circleci.com/gh/poanetwork/blockscout) [![Coverage Status](https://coveralls.io/repos/github/poanetwork/blockscout/badge.svg?branch=master)](https://coveralls.io/github/poanetwork/blockscout?branch=master) [![Join the chat at https://gitter.im/poanetwork/blockscout](https://badges.gitter.im/poanetwork/blockscout.svg)](https://gitter.im/poanetwork/blockscout?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
</div>
BlockScout provides a comprehensive, easy-to-use interface for users to view, confirm, and inspect transactions on **all EVM** (Ethereum Virtual Machine) blockchains. This includes the Ethereum main and test networks as well as **Ethereum forks and sidechains**.
Following is an overview of the project and instructions for [getting started](#getting-started).
Visit the [POA BlockScout forum](https://forum.poa.network/c/blockscout) for additional deployment instructions, FAQs, troubleshooting, and other BlockScout related items. You can also post and answer questions here.
You can also access the dev chatroom on our [Gitter Channel](https://gitter.im/poanetwork/blockscout).
## About BlockScout
BlockScout is an Elixir application that allows users to search transactions, view accounts and balances, and verify smart contracts on the entire Ethereum network including all forks and sidechains.
Currently available block explorers (i.e. Etherscan and Etherchain) are closed systems which are not independently verifiable. As Ethereum sidechains continue to proliferate in both private and public settings, transparent tools are needed to analyze and validate transactions.
### Features
- [x] **Open source development**: The code is community driven and available for anyone to use, explore and improve.
- [x] **Real time transaction tracking**: Transactions are updated in real time - no page refresh required. Infinite scrolling is also enabled.
- [x] **Smart contract interaction**: Users can read and verify Solidity smart contracts and access pre-existing contracts to fast-track development. Support for Vyper, LLL, and Web Assembly contracts is in progress.
- [x] **Token support**: ERC20 and ERC721 tokens are supported. Future releases will support additional token types including ERC223 and ERC1155.
- [x] **User customization**: Users can easily deploy on a network and customize the Bootstrap interface.
- [x] **Ethereum sidechain networks**: BlockScout supports the Ethereum mainnet, Ethereum testnets, POA network, and forks like Ethereum Classic, xDAI, additional sidechains, and private EVM networks.
### Supported Projects
| **Hosted Mainnets** | **Hosted Testnets** | **Additional Chains using BlockScout** |
|--------------------------------------------------------|-------------------------------------------------------|----------------------------------------------------|
| [Aerum](https://blockscout.com/aerum/mainnet) | [Goerli Testnet](https://blockscout.com/eth/goerli) | [ARTIS](https://explorer.sigma1.artis.network) |
| [Callisto](https://blockscout.com/callisto/mainnet) | [Kovan Testnet](https://blockscout.com/eth/kovan) | [Ether-1](https://blocks.ether1.wattpool.net/) |
| [Ethereum Classic](https://blockscout.com/etc/mainnet) | [POA Sokol Testnet](https://blockscout.com/poa/sokol) | [Fuse Network](https://explorer.fuse.io/) |
| [Ethereum Mainnet](https://blockscout.com/eth/mainnet) | [Rinkeby Testnet](https://blockscout.com/eth/rinkeby) | [Oasis Labs](https://blockexplorer.oasiscloud.io/) |
| [POA Core Network](https://blockscout.com/poa/core) | [Ropsten Testnet](https://blockscout.com/eth/ropsten) | [Petrichor](https://explorer.petrachor.com/) |
| [RSK](https://blockscout.com/rsk/mainnet) | | [PIRL](http://pirl.es/) |
| [xDai Chain](https://blockscout.com/poa/dai) | | [SafeChain](https://explorer.safechain.io) |
| | | [SpringChain](https://explorer.springrole.com/) |
| | | [Kotti Testnet](https://kottiexplorer.ethernode.io/) |
### Visual Interface
Interface for the POA network _updated 02/2019_
![BlockScout Example](explorer_example_2_2019.gif)
### Umbrella Project Organization
This repository is an [umbrella project](https://elixir-lang.org/getting-started/mix-otp/dependencies-and-umbrella-projects.html). Each directory under `apps/` is a separate [Mix](https://hexdocs.pm/mix/Mix.html) project and [OTP application](https://hexdocs.pm/elixir/Application.html), but the projects can use each other as a dependency in their `mix.exs`.
Each OTP application has a restricted domain.
| Directory | OTP Application | Namespace | Purpose |
|:------------------------|:--------------------|:------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `apps/ethereum_jsonrpc` | `:ethereum_jsonrpc` | `EthereumJSONRPC` | Ethereum JSONRPC client. It is allowed to know `Explorer`'s param format, but it cannot directly depend on `:explorer` |
| `apps/explorer` | `:explorer` | `Explorer` | Storage for the indexed chain. Can read and write to the backing storage. MUST be able to boot in a read-only mode when run independently from `:indexer`, so cannot depend on `:indexer` as that would start `:indexer` indexing. |
| `apps/block_scout_web` | `:block_scout_web` | `BlockScoutWeb` | Phoenix interface to `:explorer`. The minimum interface to allow web access should go in `:block_scout_web`. Any business rules or interface not tied directly to `Phoenix` or `Plug` should go in `:explorer`. MUST be able to boot in a read-only mode when run independently from `:indexer`, so cannot depend on `:indexer` as that would start `:indexer` indexing. |
| `apps/indexer` | `:indexer` | `Indexer` | Uses `:ethereum_jsonrpc` to index chain and batch import data into `:explorer`. Any process, `Task`, or `GenServer` that automatically reads from the chain and writes to `:explorer` should be in `:indexer`. This restricts automatic writes to `:indexer` and read-only mode can be achieved by not running `:indexer`. |
## Getting Started
### Requirements
| Dependency | Mac | Linux |
|-------------|-----|-------|
| [Erlang/OTP 21.0.4](https://github.com/erlang/otp) | `brew install erlang` | [Erlang Install Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L134) |
| [Elixir 1.8.1](https://elixir-lang.org/) | :point_up: | [Elixir Install Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L138) |
| [Postgres 10.3](https://www.postgresql.org/) | `brew install postgresql` | [Postgres Install Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L187) |
| [Node.js 10.x.x](https://nodejs.org/en/) | `brew install node` | [Node.js Install Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L66) |
| [Automake](https://www.gnu.org/software/automake/) | `brew install automake` | [Automake Install Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L72) |
| [Libtool](https://www.gnu.org/software/libtool/) | `brew install libtool` | [Libtool Install Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L62) |
| [Inotify-tools](https://github.com/rvoicilas/inotify-tools/wiki) | Not Required | Ubuntu - `apt-get install inotify-tools` |
| [GCC Compiler](https://gcc.gnu.org/) | `brew install gcc` | [GCC Compiler Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L70) |
| [GMP](https://gmplib.org/) | `brew install gmp` | [Install GMP Devel](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L74) |
### Build and Run
#### Playbook Deployment
We use [Ansible](https://docs.ansible.com/ansible/latest/index.html) & [Terraform](https://www.terraform.io/intro/getting-started/install.html) to build the correct infrastructure to run BlockScout. See [https://github.com/poanetwork/blockscout-terraform](https://github.com/poanetwork/blockscout-terraform) for details and instructions.
#### Manual Deployment
See [Manual BlockScout Deployment](https://forum.poa.network/t/manual-blockscout-deployment/2458) for instructions.
#### Environment Variables
Our forum contains a [full list of BlockScout environment variables](https://forum.poa.network/t/faq-blockscout-environment-variables/1814).
#### Configuring EVM Chains
* **CSS:** Update the import instruction in `apps/block_scout_web/assets/css/theme/_variables.scss` to select a preset css file. This is reflected in the `production-${chain}` branch for each instance. For example, in the `production-xdai` branch, it is set to `@import "dai-variables"`.
* **ENV:** Update the [environment variables](https://forum.poa.network/t/faq-blockscout-environment-variables/1814) to match the chain specs.
#### Automating Restarts
By default `blockscout` does not restart if it crashes. To enable automated
restarts, set the environment variable `HEART_COMMAND` to whatever command you run to start `blockscout`. Configure the heart beat timeout to change how long it waits before considering the application unresponsive. At that point, it will kill the current blockscout instance and execute the `HEART_COMMAND`. By default a crash dump is not written unless you set `ERL_CRASH_DUMP_SECONDS` to a positive or negative integer. See the [heart](http://erlang.org/doc/man/heart.html) documentation for more information.
#### CircleCI Updates
To monitor build status, configure your local [CCMenu](http://ccmenu.org/) with the following url: [`https://circleci.com/gh/poanetwork/blockscout.cc.xml?circle-token=f8823a3d0090407c11f87028c73015a331dbf604`](https://circleci.com/gh/poanetwork/blockscout.cc.xml?circle-token=f8823a3d0090407c11f87028c73015a331dbf604)
## Testing
### Requirements
* PhantomJS (for wallaby)
### Running the tests
1. Build the assets.
`cd apps/block_scout_web/assets && npm run build; cd -`
2. Format the Elixir code.
`mix format`
3. Run the test suite with coverage for whole umbrella project. This step can be run with different configuration outlined below.
`mix coveralls.html --umbrella`
4. Lint the Elixir code.
`mix credo --strict`
5. Run the dialyzer.
`mix dialyzer --halt-exit-status`
6. Check the Elixir code for vulnerabilities.
`cd apps/explorer && mix sobelow --config; cd -`
`cd apps/block_scout_web && mix sobelow --config; cd -`
7. Lint the JavaScript code.
`cd apps/block_scout_web/assets && npm run eslint; cd -`
8. Test the JavaScript code.
`cd apps/block_scout_web/assets && npm run test; cd -`
#### Parity
##### Mox
**This is the default setup. `mix coveralls.html --umbrella` will work on its own, but to be explicit, use the following setup**:
```shell
export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Parity.Mox
export ETHEREUM_JSONRPC_WEB_SOCKET_CASE=EthereumJSONRPC.WebSocket.Case.Mox
mix coveralls.html --umbrella --exclude no_parity
```
##### HTTP / WebSocket
```shell
export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Parity.HTTPWebSocket
export ETHEREUM_JSONRPC_WEB_SOCKET_CASE=EthereumJSONRPC.WebSocket.Case.Parity
mix coveralls.html --umbrella --exclude no_parity
```
| Protocol | URL |
|:----------|:-----------------------------------|
| HTTP | `http://localhost:8545` |
| WebSocket | `ws://localhost:8546` |
#### Geth
##### Mox
```shell
export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Geth.Mox
export ETHEREUM_JSONRPC_WEB_SOCKET_CASE=EthereumJSONRPC.WebSocket.Case.Mox
mix coveralls.html --umbrella --exclude no_geth
```
##### HTTP / WebSocket
```shell
export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Geth.HTTPWebSocket
export ETHEREUM_JSONRPC_WEB_SOCKET_CASE=EthereumJSONRPC.WebSocket.Case.Geth
mix coveralls.html --umbrella --exclude no_geth
```
| Protocol | URL |
|:----------|:--------------------------------------------------|
| HTTP | `https://mainnet.infura.io/8lTvJTKmHPCHazkneJsY` |
| WebSocket | `wss://mainnet.infura.io/ws/8lTvJTKmHPCHazkneJsY` |
### API Documentation
To view Modules and API Reference documentation:
1. Generate documentation.
`mix docs`
2. View the generated docs.
`open doc/index.html`
## Front-end
### Javascript
All Javascript files are under [apps/block_scout_web/assets/js](https://github.com/poanetwork/blockscout/tree/master/apps/block_scout_web/assets/js) and the main file is [app.js](https://github.com/poanetwork/blockscout/blob/master/apps/block_scout_web/assets/js/app.js). This file imports all javascript used in the application. If you want to create a new JS file consider creating into [/js/pages](https://github.com/poanetwork/blockscout/tree/master/apps/block_scout_web/assets/js/pages) or [/js/lib](https://github.com/poanetwork/blockscout/tree/master/apps/block_scout_web/assets/js/lib), as follows:
#### js/lib
This folder contains all scripts that can be reused in any page or can be used as a helper to some component.
#### js/pages
This folder contains the scripts that are specific for some page.
#### Redux
This project uses Redux to control the state in some pages. There are pages that have things happening in real-time thanks to the Phoenix channels, e.g. Address page, so the page state changes a lot depending on which events it is listening. The redux is also used to load some contents asynchronous, see [async_listing_load.js](https://github.com/poanetwork/blockscout/blob/master/apps/block_scout_web/assets/js/lib/async_listing_load.js).
To understand how to build new pages that need redux in this project, see the [redux_helpers.js](https://github.com/poanetwork/blockscout/blob/master/apps/block_scout_web/assets/js/lib/redux_helpers.js)
## Internationalization
The app is currently internationalized. It is only localized to U.S. English. To translate new strings.
1. To setup translation file.
`cd apps/block_scout_web; mix gettext.extract --merge; cd -`
2. To edit the new strings, go to `apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po`.
## Metrics
BlockScout is setup to export [Prometheus](https://prometheus.io/) metrics at `/metrics`.
### Prometheus
1. Install prometheus: `brew install prometheus`
2. Start the web server `iex -S mix phx.server`
3. Start prometheus: `prometheus --config.file=prometheus.yml`
### Grafana
1. Install grafana: `brew install grafana`
2. Install Pie Chart panel plugin: `grafana-cli plugins install grafana-piechart-panel`
3. Start grafana: `brew services start grafana`
4. Add Prometheus as a Data Source
1. `open http://localhost:3000/datasources`
2. Click "+ Add data source"
3. Put "Prometheus" for "Name"
4. Change "Type" to "Prometheus"
5. Set "URL" to "http://localhost:9090"
6. Set "Scrape Interval" to "10s"
5. Add the dashboards from https://github.com/deadtrickster/beam-dashboards:
For each `*.json` file in the repo.
1. `open http://localhost:3000/dashboard/import`
2. Copy the contents of the JSON file in the "Or paste JSON" entry
3. Click "Load"
6. View the dashboards. (You will need to click-around and use BlockScout for the web-related metrics to show up.)
## Tracing
Blockscout supports tracing via
[Spandex](http://git@github.com:spandex-project/spandex.git). Each application
has its own tracer, that is configured internally to that application. In order
to enable it, visit each application's `config/<env>.ex` and update its tracer
configuration to change `disabled?: true` to `disabled?: false`. Do this for
each application you'd like included in your trace data.
Currently, only [Datadog](https://www.datadoghq.com/) is supported as a
tracing backend, but more will be added soon.
### DataDog
If you would like to use DataDog, after enabling `Spandex`, set
`"DATADOG_HOST"` and `"DATADOG_PORT"` environment variables to the
host/port that your Datadog agent is running on. For more information on
Datadog and the Datadog agent, see their
[documentation](https://docs.datadoghq.com/).
### Other
If you want to use a different backend, remove the
`SpandexDatadog.ApiServer` `Supervisor.child_spec` from
`Explorer.Application` and follow any instructions provided in `Spandex`
for setting up that backend.
## Memory Usage
The work queues for building the index of all blocks, balances (coin and token), and internal transactions can grow quite large. By default, the soft-limit is 1 GiB, which can be changed in `apps/indexer/config/config.exs`:
```
config :indexer, memory_limit: 1 <<< 30
```
Memory usage is checked once per minute. If the soft-limit is reached, the shrinkable work queues will shed half their load. The shed load will be restored from the database, the same as when a restart of the server occurs, so rebuilding the work queue will be slower, but use less memory.
If all queues are at their minimum size, then no more memory can be reclaimed and an error will be logged.
## Acknowledgements
We would like to thank the [EthPrize foundation](http://ethprize.io/) for their funding support.
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution and pull request protocol. We expect contributors to follow our [code of conduct](CODE_OF_CONDUCT.md) when submitting code or comments.
## License
[![License: GPL v3.0](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
This project is licensed under the GNU General Public License v3.0. See the [LICENSE](LICENSE) file for details.

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="description" content="Description">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/vue.css">
</head>
<body>
<div id="app"></div>
<script>
window.$docsify = {
name: '',
repo: ''
}
</script>
<script src="//unpkg.com/docsify/lib/docsify.min.js"></script>
</body>
</html>
Loading…
Cancel
Save