Merge pull request #738 from Lokraan/validated-blocks-address-page-overview

Add mined blocks count to miner (validator) address page overview.
pull/826/head
Andrew Cravenho 6 years ago committed by GitHub
commit 32ce2f9289
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 24
      apps/block_scout_web/assets/js/pages/address.js
  2. 3
      apps/block_scout_web/lib/block_scout_web/channels/block_channel.ex
  3. 5
      apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex
  4. 4
      apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex
  5. 6
      apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex
  6. 5
      apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex
  7. 3
      apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex
  8. 5
      apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex
  9. 5
      apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex
  10. 43
      apps/block_scout_web/lib/block_scout_web/controllers/address_validation_controller.ex
  11. 7
      apps/block_scout_web/lib/block_scout_web/router.ex
  12. 12
      apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex
  13. 22
      apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex
  14. 25
      apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex
  15. 13
      apps/block_scout_web/lib/block_scout_web/templates/address_read_contract/index.html.eex
  16. 27
      apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex
  17. 27
      apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex
  18. 137
      apps/block_scout_web/lib/block_scout_web/templates/address_validation/index.html.eex
  19. 2
      apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex
  20. 94
      apps/block_scout_web/lib/block_scout_web/templates/tokens/token/show.html.eex
  21. 4
      apps/block_scout_web/lib/block_scout_web/templates/transaction/index.html.eex
  22. 2
      apps/block_scout_web/lib/block_scout_web/views/address_internal_transaction_view.ex
  23. 2
      apps/block_scout_web/lib/block_scout_web/views/address_read_contract_view.ex
  24. 2
      apps/block_scout_web/lib/block_scout_web/views/address_transaction_view.ex
  25. 6
      apps/block_scout_web/lib/block_scout_web/views/address_validation_view.ex
  26. 4
      apps/block_scout_web/lib/block_scout_web/views/address_view.ex
  27. 1457
      apps/block_scout_web/priv/gettext/default.pot
  28. 1479
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  29. 15
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex
  30. 44
      apps/explorer/lib/explorer/chain.ex
  31. 3
      apps/explorer/lib/explorer/etherscan.ex
  32. 62
      apps/explorer/test/explorer/chain_test.exs
  33. 3
      apps/explorer/test/support/factory.ex

@ -93,6 +93,14 @@ export function reducer (state = initialState, action) {
})
}
}
case 'RECEIVED_NEW_BLOCK': {
if (state.channelDisconnected || state.beyondPageOne) return state
return Object.assign({}, state, {
newBlock: action.msg.blockHtml,
minerHash: action.msg.blockMinerHash
})
}
default:
return state
}
@ -116,6 +124,12 @@ if ($addressDetailsPage.length) {
addressChannel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' }))
addressChannel.on('balance', (msg) => store.dispatch({ type: 'RECEIVED_UPDATED_BALANCE', msg }))
if (!state.beyondPageOne) {
const blocksChannel = socket.channel(`blocks:new_block`, {})
blocksChannel.join()
blocksChannel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' }))
blocksChannel.on('new_block', (msg) => {
store.dispatch({ type: 'RECEIVED_NEW_BLOCK', msg: humps.camelizeKeys(msg) })
})
addressChannel.on('transaction', batchChannel((msgs) =>
store.dispatch({ type: 'RECEIVED_NEW_TRANSACTION_BATCH', msgs })
))
@ -135,6 +149,7 @@ if ($addressDetailsPage.length) {
const $internalTransactionsList = $('[data-selector="internal-transactions-list"]')
const $transactionCount = $('[data-selector="transaction-count"]')
const $transactionsList = $('[data-selector="transactions-list"]')
const $validationsList = $('[data-selector="validations-list"]')
if ($emptyInternalTransactionsList.length && state.newInternalTransactions.length) window.location.reload()
if ($emptyTransactionsList.length && state.newTransactions.length) window.location.reload()
@ -159,6 +174,15 @@ if ($addressDetailsPage.length) {
prependWithClingBottom($transactionsList, state.newTransactions.slice(oldState.newTransactions.length).reverse().join(''))
updateAllAges()
}
if (oldState.newBlock !== state.newBlock && state.minerHash === state.addressHash) {
const len = $validationsList.children().length
$validationsList
.children()
.slice(len - 1, len)
.remove()
$validationsList.prepend(state.newBlock)
}
}
})
}

@ -34,7 +34,8 @@ defmodule BlockScoutWeb.BlockChannel do
average_block_time: Timex.format_duration(average_block_time, :humanized),
chain_block_html: rendered_chain_block,
block_html: rendered_block,
block_number: block.number
block_number: block.number,
block_miner_hash: to_string(block.miner_hash)
})
{:noreply, socket}

@ -1,7 +1,7 @@
defmodule BlockScoutWeb.AddressContractController do
use BlockScoutWeb, :controller
import BlockScoutWeb.AddressController, only: [transaction_count: 1]
import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1]
alias Explorer.{Chain, Market}
alias Explorer.ExchangeRates.Token
@ -14,7 +14,8 @@ defmodule BlockScoutWeb.AddressContractController do
"index.html",
address: address,
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
transaction_count: transaction_count(address)
transaction_count: transaction_count(address),
validation_count: validation_count(address)
)
else
:error ->

@ -20,4 +20,8 @@ defmodule BlockScoutWeb.AddressController do
def transaction_count(%Address{} = address) do
Chain.address_to_transactions_estimated_count(address)
end
def validation_count(%Address{} = address) do
Chain.address_to_validation_count(address)
end
end

@ -5,7 +5,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do
use BlockScoutWeb, :controller
import BlockScoutWeb.AddressController, only: [transaction_count: 1]
import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1]
import BlockScoutWeb.Chain, only: [current_filter: 1, paging_options: 1, next_page_params: 3, split_list_by_page: 1]
alias Explorer.{Chain, Market}
@ -26,7 +26,6 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do
|> Keyword.merge(current_filter(params))
internal_transactions_plus_one = Chain.address_to_internal_transactions(address, full_options)
{internal_transactions, next_page} = split_list_by_page(internal_transactions_plus_one)
render(
@ -37,7 +36,8 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
filter: params["filter"],
internal_transactions: internal_transactions,
transaction_count: transaction_count(address)
transaction_count: transaction_count(address),
validation_count: validation_count(address)
)
else
:error ->

@ -11,7 +11,7 @@ defmodule BlockScoutWeb.AddressReadContractController do
alias Explorer.{Chain, Market}
alias Explorer.ExchangeRates.Token
import BlockScoutWeb.AddressController, only: [transaction_count: 1]
import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1]
def index(conn, %{"address_id" => address_hash_string}) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
@ -21,7 +21,8 @@ defmodule BlockScoutWeb.AddressReadContractController do
"index.html",
address: address,
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
transaction_count: transaction_count(address)
transaction_count: transaction_count(address),
validation_count: validation_count(address)
)
else
:error ->

@ -4,7 +4,7 @@ defmodule BlockScoutWeb.AddressTokenController do
alias Explorer.{Chain, Market}
alias Explorer.ExchangeRates.Token
import BlockScoutWeb.AddressController, only: [transaction_count: 1]
import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1]
import BlockScoutWeb.Chain, only: [next_page_params: 3, paging_options: 1, split_list_by_page: 1]
def index(conn, %{"address_id" => address_hash_string} = params) do
@ -19,6 +19,7 @@ defmodule BlockScoutWeb.AddressTokenController do
address: address,
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
transaction_count: transaction_count(address),
validation_count: validation_count(address),
next_page_params: next_page_params(next_page, tokens, params),
tokens: tokens
)

@ -4,7 +4,7 @@ defmodule BlockScoutWeb.AddressTokenTransferController do
alias Explorer.{Chain, Market}
alias Explorer.ExchangeRates.Token
import BlockScoutWeb.AddressController, only: [transaction_count: 1]
import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1]
import BlockScoutWeb.Chain,
only: [next_page_params: 3, paging_options: 1, split_list_by_page: 1]
@ -34,7 +34,8 @@ defmodule BlockScoutWeb.AddressTokenTransferController do
next_page_params: next_page_params(next_page, transactions_paginated, params),
token: token,
transaction_count: transaction_count(address),
transactions: transactions_paginated
transactions: transactions_paginated,
validation_count: validation_count(address)
)
else
:error ->

@ -5,7 +5,7 @@ defmodule BlockScoutWeb.AddressTransactionController do
use BlockScoutWeb, :controller
import BlockScoutWeb.AddressController, only: [transaction_count: 1]
import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1]
import BlockScoutWeb.Chain, only: [current_filter: 1, paging_options: 1, next_page_params: 3, split_list_by_page: 1]
alias Explorer.{Chain, Market}
@ -37,7 +37,8 @@ defmodule BlockScoutWeb.AddressTransactionController do
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
filter: params["filter"],
transactions: transactions,
transaction_count: transaction_count(address)
transaction_count: transaction_count(address),
validation_count: validation_count(address)
)
else
:error ->

@ -0,0 +1,43 @@
defmodule BlockScoutWeb.AddressValidationController do
@moduledoc """
Display all the blocks that this address validates.
"""
use BlockScoutWeb, :controller
import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1]
import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1]
alias Explorer.{Chain, Market}
alias Explorer.ExchangeRates.Token
def index(conn, %{"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
full_options =
Keyword.merge(
[necessity_by_association: %{miner: :required, transactions: :optional}],
paging_options(params)
)
blocks_plus_one = Chain.get_blocks_validated_by_address(full_options, address)
{blocks, next_page} = split_list_by_page(blocks_plus_one)
render(
conn,
"index.html",
address: address,
blocks: blocks,
transaction_count: transaction_count(address),
validation_count: validation_count(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
next_page_params: next_page_params(next_page, blocks, params)
)
else
:error ->
unprocessable_entity(conn)
{:error, :not_found} ->
not_found(conn)
end
end
end

@ -78,6 +78,13 @@ defmodule BlockScoutWeb.Router do
as: :internal_transaction
)
resources(
"/validations",
AddressValidationController,
only: [:index],
as: :validation
)
resources(
"/contracts",
AddressContractController,

@ -17,15 +17,19 @@
</div>
<h1 class="card-title"><%= address_title(@address) %> <%= gettext "Details" %> </h1>
<h3 class="<%= if BlockScoutWeb.AddressView.contract?(@address) do %>contract-address<% end %>" data-test="address_detail_hash"><%= @address.hash %></h3>
<div class="d-flex flex-column flex-lg-row justify-content-start text-muted">
<%= if address_name = primary_name(@address) do %>
<strong class="mr-4 mb-2 text-primary"><%= address_name %></strong>
<% end %>
<span class="mr-4 mb-2">
<span data-selector="transaction-count"><%= Cldr.Number.to_string!(@transaction_count, format: "#,###") %></span>
<%= gettext "Transactions" %>
<span data-selector="transaction-count">
<%= Cldr.Number.to_string!(@transaction_count, format: "#,###") %>
</span> <%= gettext("Transactions") %>
<%= if validator?(@validation_count) do %>
<span data-selector="validation-count">
<%= Cldr.Number.to_string!(@validation_count, format: "#,###") %>
</span> <%= gettext("Blocks Validated") %>
<% end %>
</span>
<%= if @address.token do %>
<span class="mr-4 mb-2">

@ -29,6 +29,16 @@
to: address_internal_transaction_path(@conn, :index, @address.hash)
) %>
</li>
<%= if BlockScoutWeb.AddressView.validator?(@validation_count) do %>
<li class="nav-item">
<%= link(
gettext("Blocks Validated"),
class: "nav-link",
"data-test": "validations_tab_link",
to: address_validation_path(@conn, :index, @address.hash)
) %>
</li>
<% end %>
<li class="nav-item">
<%= link(
to: address_contract_path(@conn, :index, @address.hash),
@ -45,7 +55,8 @@
<%= link(
gettext("Read Contract"),
to: address_read_contract_path(@conn, :index, @address.hash),
class: "nav-link")%>
class: "nav-link"
)%>
</li>
<% end %>
</ul>
@ -77,6 +88,14 @@
"data-test": "internal_transactions_tab_link",
to: address_internal_transaction_path(@conn, :index, @address.hash)
) %>
<%= if BlockScoutWeb.AddressView.validator?(@validation_count) do %>
<%= link(
gettext("Blocks Validated"),
class: "dropdown-item",
"data-test": "validations_tab_link",
to: address_validation_path(@conn, :index, @address.hash)
) %>
<% end %>
<%= link(
to: address_contract_path(@conn, :index, @address.hash),
class: "dropdown-item active") do %>
@ -155,7 +174,6 @@
</div>
</section>
<% end %>
<section>
<div class="d-flex justify-content-between align-items-baseline">

@ -30,6 +30,16 @@
to: address_internal_transaction_path(@conn, :index, @address.hash)
) %>
</li>
<%= if validator?(@validation_count) do %>
<li class="nav-item">
<%= link(
gettext("Blocks Validated"),
class: "nav-link",
"data-test": "validations_tab_link",
to: address_validation_path(@conn, :index, @address.hash)
) %>
</li>
<% end %>
<%= if contract?(@address) do %>
<li class="nav-item">
<%= link(
@ -76,6 +86,15 @@
"data-test": "internal_transactions_tab_link",
to: address_internal_transaction_path(@conn, :index, @address.hash)
) %>
<%= if validator?(@validation_count) do %>
<%= link(
gettext("Blocks Validated"),
class: "dropdown-item",
"data-test": "validations_tab_link",
to: address_validation_path(@conn, :index, @address.hash)
) %>
<% end %>
<%= if contract?(@address) do %>
<%= link(
to: address_contract_path(@conn, :index, @address.hash),
class: "dropdown-item") do %>
@ -85,6 +104,7 @@
<i class="far fa-check-circle"></i>
<% end %>
<% end %>
<% end %>
<%= link(
gettext("Read Contract"),
to: address_read_contract_path(@conn, :index, @address.hash),
@ -94,12 +114,12 @@
</ul>
</div>
<div class="card-body">
<div data-selector="channel-batching-message" style="display:none;">
<div data-selector="channel-batching-message" class="d-none">
<div data-selector="reload-button" class="alert alert-info">
<a href="#" class="alert-link"><span data-selector="channel-batching-count"></span> <%= gettext "More internal transactions have come in" %></a>
</div>
</div>
<div data-selector="channel-disconnected-message" style="display:none;">
<div data-selector="channel-disconnected-message" class="d-none">
<div data-selector="reload-button" class="alert alert-danger">
<a href="#" class="alert-link"><%= gettext "Connection Lost, click to load newer internal transactions" %></a>
</div>
@ -168,6 +188,5 @@
</div>
</div>
</div>
</section>
</section>

@ -29,6 +29,16 @@
to: address_internal_transaction_path(@conn, :index, @address.hash)
) %>
</li>
<%= if validator?(@validation_count) do %>
<li class="nav-item">
<%= link(
gettext("Blocks Validated"),
class: "nav-link",
"data-test": "validations_tab_link",
to: address_validation_path(@conn, :index, @address.hash)
) %>
</li>
<% end %>
<li class="nav-item">
<%= link(
to: address_contract_path(@conn, :index, @address.hash),
@ -44,7 +54,8 @@
<%= link(
gettext("Read Contract"),
to: address_read_contract_path(@conn, :index, @address.hash),
class: "nav-link active")%>
class: "nav-link active"
)%>
</li>
</ul>

@ -13,7 +13,6 @@
to: address_transaction_path(@conn, :index, @address.hash)
) %>
</li>
<li class="nav-item">
<%= link(
gettext("Tokens"),
@ -21,15 +20,24 @@
to: address_token_path(@conn, :index, @address.hash)
) %>
</li>
<li class="nav-item"> <%= link(
<li class="nav-item">
<%= link(
gettext("Internal Transactions"),
class: "nav-link",
"data-test": "internal_transactions_tab_link",
to: address_internal_transaction_path(@conn, :index, @address.hash)
) %>
</li>
<%= if AddressView.validator?(@validation_count) do %>
<li class="nav-item">
<%= link(
gettext("Blocks Validated"),
class: "nav-link",
"data-test": "validations_tab_link",
to: address_validation_path(@conn, :index, @address.hash)
) %>
</li>
<% end %>
<%= if AddressView.contract?(@address) do %>
<li class="nav-item">
<%= link(
@ -43,7 +51,6 @@
<% end %>
</li>
<% end %>
<%= if AddressView.smart_contract_with_read_only_functions?(@address) do %>
<li class="nav-item">
<%= link(
@ -85,6 +92,15 @@
"data-test": "internal_transactions_tab_link",
to: address_internal_transaction_path(@conn, :index, @address.hash)
) %>
<%= if AddressView.validator?(@validation_count) do %>
<%= link(
gettext("Blocks Validated"),
class: "dropdown-item",
"data-test": "validations_tab_link",
to: address_validation_path(@conn, :index, @address.hash)
) %>
<% end %>
<%= if AddressView.contract?(@address) do %>
<%= link(
to: address_contract_path(@conn, :index, @address.hash),
class: "dropdown-item") do %>
@ -94,6 +110,7 @@
<i class="far fa-check-circle"></i>
<% end %>
<% end %>
<% end %>
<%= if AddressView.smart_contract_with_read_only_functions?(@address) do %>
<%= link(
gettext("Read Contract"),

@ -31,6 +31,16 @@
to: address_internal_transaction_path(@conn, :index, @address.hash)
) %>
</li>
<%= if validator?(@validation_count) do %>
<li class="nav-item">
<%= link(
gettext("Blocks Validated"),
class: "nav-link",
"data-test": "validations_tab_link",
to: address_validation_path(@conn, :index, @address.hash)
) %>
</li>
<% end %>
<%= if contract?(@address) do %>
<li class="nav-item">
<%= link(
@ -49,7 +59,8 @@
<%= link(
gettext("Read Contract"),
to: address_read_contract_path(@conn, :index, @address.hash),
class: "nav-link")%>
class: "nav-link"
)%>
</li>
<% end %>
</ul>
@ -75,6 +86,14 @@
"data-test": "internal_transactions_tab_link",
to: address_internal_transaction_path(@conn, :index, @address.hash)
) %>
<%= if validator?(@validation_count) do %>
<%= link(
gettext("Blocks Validated"),
class: "dropdown-item",
"data-test": "validations_tab_link",
to: address_validation_path(@conn, :index, @address.hash)
) %>
<% end %>
<%= if contract?(@address) do %>
<%= link(
to: address_contract_path(@conn, :index, @address.hash),
@ -90,7 +109,7 @@
<%= link(
gettext("Read Contract"),
to: address_read_contract_path(@conn, :index, @address.hash),
class: "dropdown-item")%>
class: "dropdown-item") %>
<% end %>
</div>
</li>
@ -98,12 +117,12 @@
</div>
<div class="card-body">
<div data-selector="channel-batching-message" style="display:none;">
<div data-selector="channel-batching-message" class="d-none">
<div data-selector="reload-button" class="alert alert-info">
<a href="#" class="alert-link"><span data-selector="channel-batching-count"></span> <%= gettext "More transactions have come in" %></a>
</div>
</div>
<div data-selector="channel-disconnected-message" style="display:none;">
<div data-selector="channel-disconnected-message" class="d-none">
<div data-selector="reload-button" class="alert alert-danger">
<a href="#" class="alert-link"><%= gettext "Connection Lost, click to load newer transactions" %></a>
</div>

@ -0,0 +1,137 @@
<section class="container">
<%= render BlockScoutWeb.AddressView, "overview.html", assigns %>
<section>
<div class="card">
<div class="card-header">
<!-- DESKTOP TAB NAV -->
<ul class="nav nav-tabs card-header-tabs d-none d-md-inline-flex">
<li class="nav-item">
<%= link(
gettext("Transactions"),
class: "nav-link",
to: address_transaction_path(@conn, :index, @address.hash)
) %>
</li>
<li class="nav-item">
<%= link(
gettext("Tokens"),
class: "nav-link",
to: address_token_path(@conn, :index, @address.hash)
) %>
</li>
<li class="nav-item"> <%= link(
gettext("Internal Transactions"),
class: "nav-link",
"data-test": "internal_transactions_tab_link",
to: address_internal_transaction_path(@conn, :index, @address.hash)
) %>
</li>
<li class="nav-item">
<%= link(
gettext("Blocks Validated"),
class: "nav-link active",
"data-test": "validations_tab_link",
to: address_validation_path(@conn, :index, @address.hash)
) %>
</li>
<%= if contract?(@address) do %>
<li class="nav-item">
<%= link(
to: address_contract_path(@conn, :index, @address.hash),
class: "nav-link") do %>
<%= gettext("Code") %>
<%= if smart_contract_verified?(@address) do %>
<i class="far fa-check-circle"></i>
<% end %>
<% end %>
</li>
<% end %>
<%= if smart_contract_with_read_only_functions?(@address) do %>
<li class="nav-item">
<%= link(
gettext("Read Contract"),
to: address_read_contract_path(@conn, :index, @address.hash),
class: "nav-link")%>
</li>
<% end %>
</ul>
<!-- MOBILE DROPDOWN NAV -->
<ul class="nav nav-tabs card-header-tabs d-md-none">
<li class="nav-item dropdown flex-fill text-center">
<a class="nav-link active dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false"><%= gettext "Tokens" %></a>
<div class="dropdown-menu">
<%= link(
gettext("Transactions"),
class: "dropdown-item",
to: address_transaction_path(@conn, :index, @address.hash)
) %>
<%= link(
gettext("Tokens"),
class: "dropdown-item",
to: address_token_path(@conn, :index, @address.hash)
) %>
<%= link(
gettext("Internal Transactions"),
class: "dropdown-item",
"data-test": "internal_transactions_tab_link",
to: address_internal_transaction_path(@conn, :index, @address.hash)
) %>
<%= link(
gettext("Blocks Validated"),
class: "dropdown-item active",
"data-test": "validations_tab_link",
to: address_validation_path(@conn, :index, @address.hash)
) %>
<%= if contract?(@address) do %>
<%= link(
to: address_contract_path(@conn, :index, @address.hash),
class: "dropdown-item") do %>
<%= gettext("Code") %>
<%= if smart_contract_verified?(@address) do %>
<i class="far fa-check-circle"></i>
<% end %>
<% end %>
<% end %>
</div>
</li>
</ul>
</div>
<div class="card-body">
<div data-selector="channel-batching-message">
<div data-selector="reload-button" class="alert alert-info" class="d-none">
<a href="#" class="alert-link"><span data-selector="channel-batching-count"></span> <%= gettext "More validations have come in." %></a>
</div>
</div>
<div data-selector="channel-disconnected-message" class="d-none">
<div data-selector="reload-button" class="alert alert-danger">
<a href="#" class="alert-link"><%= gettext "Connection Lost, click to load newer validations" %></a>
</div>
</div>
<h2 class="card-title"><%=gettext("Blocks Validated")%></h2>
<span data-selector="validations-list">
<%= for block <- @blocks do %>
<%= render BlockScoutWeb.BlockView, "_tile.html", block: block %>
<% end %>
</span>
<div>
<%= if @next_page_params do %>
<%= link(
gettext("Older"),
class: "button button-secondary button-sm float-right mt-3",
to: address_validation_path(
@conn,
:index,
@address,
@next_page_params
)
) %>
<% end %>
</div>
</div> <!-- Card Body -->
</div> <!-- Card -->
<section>
</section>

@ -64,7 +64,7 @@
<div class="card card-chain-transactions">
<div class="card-body">
<div data-selector="channel-batching-message" style="display:none;">
<div data-selector="channel-batching-message" class="d-none">
<div data-selector="reload-button" class="alert alert-info">
<a href="#" class="alert-link"><span data-selector="channel-batching-count"></span> <%= gettext "More transactions have come in" %></a>
</div>

@ -0,0 +1,94 @@
<section class="container">
<%= render(
OverviewView,
"_details.html",
token: @token,
total_token_transfers: @total_token_transfers,
total_token_holders: @total_token_holders,
conn: @conn
) %>
<section>
<div class="card">
<div class="card-header">
<!-- DESKTOP TAB NAV -->
<ul class="nav nav-tabs card-header-tabs d-none d-md-inline-flex">
<li class="nav-item">
<%= link(
gettext("Token Transfers"),
class: "nav-link active",
to: token_path(@conn, :show, @token.contract_address_hash)
) %>
</li>
<%= if smart_contract_with_read_only_functions?(@token) do %>
<li class="nav-item">
<%= link(
gettext("Read Contract"),
to: token_read_contract_path(@conn, :index, @conn.params["id"]),
class: "nav-link")%>
</li>
<% end %>
<li class="nav-item"i>
<%= link(
gettext("Token Holders"),
class: "nav-link",
"data-test": "token_holders_tab",
to: token_holder_path(@conn, :index, @token.contract_address_hash)
) %>
</li>
</ul>
<!-- MOBILE DROPDOWN NAV -->
<ul class="nav nav-tabs card-header-tabs d-md-none">
<li class="nav-item dropdown flex-fill text-center">
<a class="nav-link active dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false"><%= gettext("Token Transfers") %></a>
<div class="dropdown-menu">
<%= link(
gettext("Token Transfers"),
class: "dropdown-item active",
to: token_path(@conn, :show, @token.contract_address_hash)
) %>
<%= if smart_contract_with_read_only_functions?(@token) do %>
<%= link(
gettext("Read Contract"),
to: "#",
class: "dropdown-item")%>
<% end %>
<%= link(
gettext("Token Holders"),
class: "dropdown-item",
to: token_holder_path(@conn, :index, @token.contract_address_hash)
) %>
</div>
</li>
</ul>
</div>
<div class="card-body">
<h2 class="card-title"><%= gettext "Token Transfers" %></h2>
<%= if Enum.any?(@transfers) do %>
<%= for transfer <- @transfers do %>
<%= render("_token_transfer.html", token: @token, transfer: transfer) %>
<% end %>
<% else %>
<div class="tile tile-muted text-center">
<span data-selector="empty-transactions-list">
<%= gettext "There are no transfers for this Token." %>
</span>
</div>
<% end %>
<%= if @next_page_params do %>
<%= link(
gettext("Older"),
class: "button button-secondary button-small float-right mt-4",
to: token_path(@conn, :show, @token.contract_address_hash, @next_page_params)
) %>
<% end %>
</div>
</div>
</section>
</section>

@ -43,12 +43,12 @@
</div>
<div class="card-body">
<div data-selector="channel-batching-message" style="display:none;">
<div data-selector="channel-batching-message" class="d-none">
<div data-selector="reload-button" class="alert alert-info">
<a href="#" class="alert-link"><span data-selector="channel-batching-count"></span> <%= gettext "More transactions have come in" %></a>
</div>
</div>
<div data-selector="channel-disconnected-message" style="display:none;">
<div data-selector="channel-disconnected-message" class="d-none">
<div data-selector="reload-button" class="alert alert-danger">
<a href="#" class="alert-link"><%= gettext "Connection Lost, click to load newer transactions" %></a>
</div>

@ -2,7 +2,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionView do
use BlockScoutWeb, :view
import BlockScoutWeb.AddressView,
only: [contract?: 1, smart_contract_verified?: 1, smart_contract_with_read_only_functions?: 1]
only: [contract?: 1, smart_contract_verified?: 1, smart_contract_with_read_only_functions?: 1, validator?: 1]
def format_current_filter(filter) do
case filter do

@ -1,7 +1,7 @@
defmodule BlockScoutWeb.AddressReadContractView do
use BlockScoutWeb, :view
import BlockScoutWeb.AddressView, only: [smart_contract_verified?: 1]
import BlockScoutWeb.AddressView, only: [smart_contract_verified?: 1, validator?: 1]
def queryable?(inputs), do: Enum.any?(inputs)

@ -2,7 +2,7 @@ defmodule BlockScoutWeb.AddressTransactionView do
use BlockScoutWeb, :view
import BlockScoutWeb.AddressView,
only: [contract?: 1, smart_contract_verified?: 1, smart_contract_with_read_only_functions?: 1]
only: [contract?: 1, smart_contract_verified?: 1, smart_contract_with_read_only_functions?: 1, validator?: 1]
def format_current_filter(filter) do
case filter do

@ -0,0 +1,6 @@
defmodule BlockScoutWeb.AddressValidationView do
use BlockScoutWeb, :view
import BlockScoutWeb.AddressView,
only: [contract?: 1, smart_contract_verified?: 1, smart_contract_with_read_only_functions?: 1]
end

@ -111,6 +111,10 @@ defmodule BlockScoutWeb.AddressView do
def contract?(nil), do: true
def validator?(val) when val > 0, do: true
def validator?(_), do: false
def hash(%Address{hash: hash}) do
to_string(hash)
end

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -258,11 +258,18 @@ defmodule EthereumJSONRPC.Receipt do
defp entry_to_elixir({"status" = key, status}) do
case status do
"0x0" -> {:ok, {key, :error}}
"0x1" -> {:ok, {key, :ok}}
"0x0" ->
{:ok, {key, :error}}
"0x1" ->
{:ok, {key, :ok}}
# pre-Byzantium / Ethereum Classic on Parity
nil -> :ignore
other -> {:error, {:unknown_value, %{key: key, value: other}}}
nil ->
:ignore
other ->
{:error, {:unknown_value, %{key: key, value: other}}}
end
end

@ -12,7 +12,8 @@ defmodule Explorer.Chain do
order_by: 3,
preload: 2,
where: 2,
where: 3
where: 3,
select: 3
]
alias Ecto.Adapters.SQL
@ -837,6 +838,47 @@ defmodule Explorer.Chain do
|> Repo.all()
end
@doc """
Finds all Blocks validated by the address given.
## Options
* `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is
`:required`, and the `t:Explorer.Chain.Block.t/0` has no associated record for that association, then the
`t:Explorer.Chain.Block.t/0` will not be included in the page `entries`.
* `:paging_options` - a `t:Explorer.PagingOptions.t/0` used to specify the `:page_size` and
`:key` (a tuple of the lowest/oldest `{block_number}`) and. Results will be the internal
transactions older than the `block_number` that are passed.
Returns all blocks validated by the address given.
"""
@spec get_blocks_validated_by_address(
[paging_options | necessity_by_association_option],
Address.t()
) :: [Block.t()]
def get_blocks_validated_by_address(options \\ [], %Address{hash: hash}) when is_list(options) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
Block
|> join_associations(necessity_by_association)
|> where(miner_hash: ^hash)
|> page_blocks(paging_options)
|> limit(^paging_options.page_size)
|> order_by(desc: :number)
|> Repo.all()
end
@doc """
Counts the number of `t:Explorer.Chain.Block.t/0` validated by the `address`.
"""
@spec address_to_validation_count(Address.t()) :: non_neg_integer()
def address_to_validation_count(%Address{hash: hash}) do
Block
|> where(miner_hash: ^hash)
|> select([b], count(b.hash))
|> Repo.one()
end
@doc """
Returns a stream of unfetched `t:Explorer.Chain.Address.CoinBalance.t/0`.

@ -172,7 +172,8 @@ defmodule Explorer.Etherscan do
%Hash{byte_count: unquote(Hash.Address.byte_count())} = address_hash
) do
query =
from(tb in TokenBalance,
from(
tb in TokenBalance,
where: tb.token_contract_address_hash == ^contract_address_hash,
where: tb.address_hash == ^address_hash,
order_by: [desc: :block_number],

@ -1033,6 +1033,68 @@ defmodule Explorer.ChainTest do
end
end
describe "get_blocks_validated_by_address/2" do
test "returns nothing when there are no blocks" do
address = insert(:address)
assert [] = Chain.get_blocks_validated_by_address(address)
end
test "returns the blocks validated by a specified address" do
address = insert(:address)
another_address = insert(:address)
block = insert(:block, miner: address, miner_hash: address.hash)
insert(:block, miner: another_address, miner_hash: another_address.hash)
results =
address
|> Chain.get_blocks_validated_by_address()
|> Enum.map(& &1.hash)
assert results == [block.hash]
end
test "with blocks can be paginated" do
address = insert(:address)
first_page_block = insert(:block, miner: address, miner_hash: address.hash, number: 0)
second_page_block = insert(:block, miner: address, miner_hash: address.hash, number: 2)
assert [first_page_block.number] ==
[paging_options: %PagingOptions{key: {1}, page_size: 1}]
|> Chain.get_blocks_validated_by_address(address)
|> Enum.map(& &1.number)
|> Enum.reverse()
assert [second_page_block.number] ==
[paging_options: %PagingOptions{key: {3}, page_size: 1}]
|> Chain.get_blocks_validated_by_address(address)
|> Enum.map(& &1.number)
|> Enum.reverse()
end
end
describe "address_to_validation_count/1" do
test "returns 0 when there aren't any blocks" do
address = insert(:address)
assert 0 = Chain.address_to_validation_count(address)
end
test "returns the number of blocks mined by addres" do
address = insert(:address)
another_address = insert(:address)
insert(:block, miner: address, miner_hash: address.hash)
insert(:block, miner: another_address, miner_hash: another_address.hash)
insert(:block, miner: another_address, miner_hash: another_address.hash)
assert 1 = Chain.address_to_validation_count(address)
assert 2 = Chain.address_to_validation_count(another_address)
end
end
describe "number_to_block/1" do
test "without block" do
assert {:error, :not_found} = Chain.number_to_block(-1)

@ -50,7 +50,8 @@ defmodule Explorer.Factory do
def update_balance_value(%CoinBalance{address_hash: address_hash, block_number: block_number}, value) do
Repo.update_all(
from(balance in CoinBalance,
from(
balance in CoinBalance,
where: balance.address_hash == ^address_hash and balance.block_number == ^block_number
),
set: [value: value, value_fetched_at: DateTime.utc_now()]

Loading…
Cancel
Save