Merge branch 'master' into use-internal-transaction-init-for-contract-code

pull/1954/head
Victor Baranov 6 years ago committed by GitHub
commit ab3e9aba2e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      CHANGELOG.md
  2. 2
      README.md
  3. 8
      apps/block_scout_web/lib/block_scout_web/chain.ex
  4. 46
      apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex
  5. 5
      apps/block_scout_web/lib/block_scout_web/etherscan.ex
  6. 7
      apps/block_scout_web/lib/block_scout_web/router.ex
  7. 7
      apps/block_scout_web/lib/block_scout_web/templates/address/_tabs.html.eex
  8. 82
      apps/block_scout_web/lib/block_scout_web/templates/address_logs/index.html.eex
  9. 3
      apps/block_scout_web/lib/block_scout_web/views/address_logs_view.ex
  10. 1
      apps/block_scout_web/lib/block_scout_web/views/address_view.ex
  11. 3
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex
  12. 19
      apps/block_scout_web/priv/gettext/default.pot
  13. 19
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  14. 3
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs
  15. 8
      apps/explorer/config/config.exs
  16. 3
      apps/explorer/lib/explorer/application.ex
  17. 102
      apps/explorer/lib/explorer/chain.ex
  18. 29
      apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex
  19. 5
      apps/explorer/lib/explorer/paging_options.ex
  20. 124
      apps/explorer/lib/explorer/staking/epoch_counter.ex
  21. 23
      apps/explorer/lib/explorer/staking/pools_reader.ex
  22. 802
      apps/explorer/priv/contracts_abi/pos/staking.json
  23. 100
      apps/explorer/test/explorer/chain_test.exs
  24. 97
      apps/explorer/test/explorer/staking/epoch_counter_test.exs
  25. 21
      apps/explorer/test/explorer/staking/pools_reader_test.exs
  26. 1
      apps/indexer/lib/indexer/fetcher/staking_pools.ex
  27. 19
      apps/indexer/test/indexer/fetcher/staking_pools_test.exs

@ -22,6 +22,7 @@
- [#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
- [#1941](https://github.com/poanetwork/blockscout/pull/1941) - feat: add on demand fetching and stale attr to rpc
- [#1956](https://github.com/poanetwork/blockscout/pull/1956) - add logs tab to address
- [#1933](https://github.com/poanetwork/blockscout/pull/1933) - add eth_BlockNumber json rpc method
- [#1952](https://github.com/poanetwork/blockscout/pull/1952) - feat: exclude empty contracts by default
- [#1954](https://github.com/poanetwork/blockscout/pull/1954) - feat: use creation init on self destruct
@ -57,6 +58,7 @@
- [#1900](https://github.com/poanetwork/blockscout/pull/1900) - SUPPORTED_CHAINS ENV var
- [#1892](https://github.com/poanetwork/blockscout/pull/1892) - Remove temporary worker modules
- [#1958](https://github.com/poanetwork/blockscout/pull/1958) - Default value for release link env var
- [#1975](https://github.com/poanetwork/blockscout/pull/1975) - add log index to transaction view
## 1.3.10-beta

@ -64,7 +64,7 @@ Currently available block explorers (i.e. Etherscan and Etherchain) are closed s
* [SafeChain](https://explorer.safechain.io)
* [SpringChain](https://explorer.springrole.com/)
* [PIRL](http://pirl.es/)
* [Petrichor](https://explorer.petrichor-dev.com/)
* [Petrichor](https://explorer.petrachor.com/)
* [Ether-1](https://blocks.ether1.wattpool.net/)

@ -205,8 +205,12 @@ defmodule BlockScoutWeb.Chain do
%{"block_number" => block_number, "transaction_index" => transaction_index, "index" => index}
end
defp paging_params(%Log{index: index}) do
%{"index" => index}
defp paging_params(%Log{index: index} = log) do
if Ecto.assoc_loaded?(log.transaction) do
%{"block_number" => log.transaction.block_number, "transaction_index" => log.transaction.index, "index" => index}
else
%{"index" => index}
end
end
defp paging_params(%Transaction{block_number: nil, inserted_at: inserted_at, hash: hash}) do

@ -0,0 +1,46 @@
defmodule BlockScoutWeb.AddressLogsController do
@moduledoc """
Manages events logs tab.
"""
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
alias Indexer.Fetcher.CoinBalanceOnDemand
use BlockScoutWeb, :controller
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
logs_plus_one = Chain.address_to_logs(address, paging_options(params))
{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, next_page_params)
end
render(
conn,
"index.html",
address: address,
logs: results,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
transaction_count: transaction_count(address),
validation_count: validation_count(address),
next_page_url: next_page_url
)
else
_ ->
not_found(conn)
end
end
end

@ -594,6 +594,11 @@ defmodule BlockScoutWeb.Etherscan do
type: "block number",
definition: "A nonnegative number used to identify blocks.",
example: ~s("0x5c958")
},
index: %{
type: "log index",
definition: "A nonnegative number used to identify logs.",
example: ~s("1")
}
}
}

@ -147,6 +147,13 @@ defmodule BlockScoutWeb.Router do
as: :decompiled_contract
)
resources(
"/logs",
AddressLogsController,
only: [:index],
as: :logs
)
resources(
"/contract_verifications",
AddressContractVerificationController,

@ -22,6 +22,11 @@
"data-test": "coin_balance_tab_link",
to: address_coin_balance_path(@conn, :index, @address.hash)
) %>
<%= link(
gettext("Logs"),
class: "card-tab #{tab_status("logs", @conn.request_path)}",
to: address_logs_path(@conn, :index, @address.hash)
) %>
<%= if BlockScoutWeb.AddressView.validator?(@validation_count) do %>
<%= link(
gettext("Blocks Validated"),
@ -55,4 +60,4 @@
class: "card-tab #{tab_status("read_contract", @conn.request_path)}")
%>
<% end %>
</div>
</div>

@ -0,0 +1,82 @@
<section class="container">
<%= render BlockScoutWeb.AddressView, "overview.html", assigns %>
<div class="card">
<%= render BlockScoutWeb.AddressView, "_tabs.html", assigns %>
<div class="card-body">
<h2 class="card-title"><%= gettext "Logs" %></h2>
<%= if @next_page_url do %>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", cur_page_number: "1", show_pagination_limit: true, next_page_path: @next_page_url %>
<% end %>
<%= if !@next_page_url do %>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", cur_page_number: "1", show_pagination_limit: true %>
<% end %>
<%= if Enum.count(@logs) > 0 do %>
<%= for log <- @logs do %>
<div data-test="transaction_log" class="tile tile-muted">
<dl class="row">
<dt class="col-md-2"> <%= gettext "Transaction" %> </dt>
<dd class="col-md-10">
<h3 class="">
<%= link(
log.transaction,
to: transaction_path(@conn, :show, log.transaction),
"data-test": "log_address_link",
"data-address-hash": log.transaction
) %>
</h3>
</dd>
<dt class="col-md-2"><%= gettext "Topics" %></dt>
<dd class="col-md-10">
<div class="raw-transaction-log-topics">
<%= unless is_nil(log.first_topic) do %>
<div class="text-dark">
<span class="text-dark">[0]</span>
<%= log.first_topic %>
</div>
<% end %>
<%= unless is_nil(log.second_topic) do %>
<div class="text-dark">
<span class="">[1] </span>
<%= log.second_topic %>
</div>
<% end %>
<%= unless is_nil(log.third_topic) do %>
<div class="text-dark">
<span>[2]</span>
<%= log.third_topic %>
</div>
<% end %>
<%= unless is_nil(log.fourth_topic) do %>
<div class="text-dark">
<span>[3]</span>
<%= log.fourth_topic %>
</div>
<% end %>
</div>
</dd>
<dt class="col-md-2">
<%= gettext "Data" %>
</dt>
<dd class="col-md-10">
<%= unless is_nil(log.data) do %>
<div class="text-dark raw-transaction-log-data">
<%= log.data %>
</div>
<% end %>
</dd>
</dl>
</div>
<% end %>
<% else %>
<div class="tile tile-muted text-center">
<span><%= gettext "There are no logs for this address." %></span>
</div>
<% end %>
</div>
</div>
</section>

@ -0,0 +1,3 @@
defmodule BlockScoutWeb.AddressLogsView do
use BlockScoutWeb, :view
end

@ -300,6 +300,7 @@ defmodule BlockScoutWeb.AddressView do
defp tab_name(["read_contract"]), do: gettext("Read Contract")
defp tab_name(["coin_balances"]), do: gettext("Coin Balance History")
defp tab_name(["validations"]), do: gettext("Blocks Validated")
defp tab_name(["logs"]), do: gettext("Logs")
def short_hash(%Address{hash: hash}) do
<<

@ -77,7 +77,8 @@ defmodule BlockScoutWeb.API.RPC.TransactionView do
%{
"address" => "#{log.address_hash}",
"topics" => get_topics(log),
"data" => "#{log.data}"
"data" => "#{log.data}",
"index" => "#{log.index}"
}
end

@ -187,7 +187,7 @@ msgid "Blocks Indexed"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:27
#: 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_validation/index.html.eex:13
#: lib/block_scout_web/views/address_view.ex:302
@ -215,7 +215,7 @@ msgid "Close"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:37
#: 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:187
#: lib/block_scout_web/views/address_view.ex:298
@ -329,6 +329,7 @@ msgid "Curl"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:63
#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:18
#: lib/block_scout_web/templates/transaction_log/index.html.eex:67
#: lib/block_scout_web/templates/transaction_log/index.html.eex:133
@ -500,8 +501,11 @@ msgid "Limit"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:26
#: 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_log/index.html.eex:8
#: lib/block_scout_web/views/address_view.ex:303
#: lib/block_scout_web/views/transaction_view.ex:340
msgid "Logs"
msgstr ""
@ -677,7 +681,7 @@ msgid "Query"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:53
#: 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/views/address_view.ex:300
#: lib/block_scout_web/views/tokens/overview_view.ex:37
@ -869,6 +873,7 @@ msgid "Top Accounts - %{subnetwork} Explorer"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:33
#: lib/block_scout_web/templates/transaction_log/index.html.eex:103
msgid "Topics"
msgstr ""
@ -889,6 +894,7 @@ msgid "Total transactions"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:22
#: lib/block_scout_web/views/transaction_view.ex:287
msgid "Transaction"
msgstr ""
@ -1657,7 +1663,7 @@ msgid "Decompiled Code"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:47
#: lib/block_scout_web/templates/address/_tabs.html.eex:52
msgid "Decompiled code"
msgstr ""
@ -1766,3 +1772,8 @@ msgstr ""
#: lib/block_scout_web/templates/address_contract/index.html.eex:76
msgid "Displaying the init data provided of the creating transaction."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:77
msgid "There are no logs for this address."
msgstr ""

@ -187,7 +187,7 @@ msgid "Blocks Indexed"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:27
#: 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_validation/index.html.eex:13
#: lib/block_scout_web/views/address_view.ex:302
@ -215,7 +215,7 @@ msgid "Close"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:37
#: 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:187
#: lib/block_scout_web/views/address_view.ex:298
@ -329,6 +329,7 @@ msgid "Curl"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:63
#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:18
#: lib/block_scout_web/templates/transaction_log/index.html.eex:67
#: lib/block_scout_web/templates/transaction_log/index.html.eex:133
@ -500,8 +501,11 @@ msgid "Limit"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:26
#: 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_log/index.html.eex:8
#: lib/block_scout_web/views/address_view.ex:303
#: lib/block_scout_web/views/transaction_view.ex:340
msgid "Logs"
msgstr ""
@ -677,7 +681,7 @@ msgid "Query"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:53
#: 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/views/address_view.ex:300
#: lib/block_scout_web/views/tokens/overview_view.ex:37
@ -869,6 +873,7 @@ msgid "Top Accounts - %{subnetwork} Explorer"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:33
#: lib/block_scout_web/templates/transaction_log/index.html.eex:103
msgid "Topics"
msgstr ""
@ -889,6 +894,7 @@ msgid "Total transactions"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:22
#: lib/block_scout_web/views/transaction_view.ex:287
msgid "Transaction"
msgstr ""
@ -1657,7 +1663,7 @@ msgid "Decompiled Code"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:47
#: lib/block_scout_web/templates/address/_tabs.html.eex:52
msgid "Decompiled code"
msgstr ""
@ -1766,3 +1772,8 @@ msgstr ""
#: lib/block_scout_web/templates/address_contract/index.html.eex:76
msgid "Displaying the init data provided of the creating transaction."
msgstr ""
#, elixir-format, fuzzy
#: lib/block_scout_web/templates/address_logs/index.html.eex:77
msgid "There are no logs for this address."
msgstr ""

@ -460,7 +460,8 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
%{
"address" => "#{address.hash}",
"data" => "#{log.data}",
"topics" => ["first topic", "second topic", nil, nil]
"topics" => ["first topic", "second topic", nil, nil],
"index" => "#{log.index}"
}
],
"next_page_params" => nil

@ -60,6 +60,14 @@ config :explorer, Explorer.Staking.PoolsReader,
validators_contract_address: System.get_env("POS_VALIDATORS_CONTRACT"),
staking_contract_address: System.get_env("POS_STAKING_CONTRACT")
if System.get_env("POS_STAKING_CONTRACT") do
config :explorer, Explorer.Staking.EpochCounter,
enabled: true,
staking_contract_address: System.get_env("POS_STAKING_CONTRACT")
else
config :explorer, Explorer.Staking.EpochCounter, enabled: false
end
if System.get_env("SUPPLY_MODULE") == "TokenBridge" do
config :explorer, supply: Explorer.Chain.Supply.TokenBridge
end

@ -51,7 +51,8 @@ defmodule Explorer.Application do
configure(Explorer.Market.History.Cataloger),
configure(Explorer.Counters.AddressesWithBalanceCounter),
configure(Explorer.Counters.AverageBlockTime),
configure(Explorer.Validator.MetadataProcessor)
configure(Explorer.Validator.MetadataProcessor),
configure(Explorer.Staking.EpochCounter)
]
|> List.flatten()
end

@ -10,6 +10,7 @@ defmodule Explorer.Chain do
limit: 2,
order_by: 2,
order_by: 3,
offset: 2,
preload: 2,
select: 2,
subquery: 1,
@ -279,6 +280,38 @@ defmodule Explorer.Chain do
|> Enum.take(paging_options.page_size)
end
@spec address_to_logs(Address.t(), [paging_options]) :: [
Log.t()
]
def address_to_logs(
%Address{hash: %Hash{byte_count: unquote(Hash.Address.byte_count())} = address_hash},
options \\ []
)
when is_list(options) do
paging_options = Keyword.get(options, :paging_options) || %PagingOptions{page_size: 50}
{block_number, transaction_index, log_index} = paging_options.key || {BlockNumberCache.max_number(), 0, 0}
query =
from(log in Log,
inner_join: transaction in assoc(log, :transaction),
order_by: [desc: transaction.block_number, desc: transaction.index],
preload: [:transaction],
where:
log.address_hash == ^address_hash and
(transaction.block_number < ^block_number or
(transaction.block_number == ^block_number and transaction.index > ^transaction_index) or
(transaction.block_number == ^block_number and transaction.index == ^transaction_index and
log.index > ^log_index)),
limit: ^paging_options.page_size,
select: log
)
query
|> Repo.all()
|> Enum.take(paging_options.page_size)
end
@doc """
Finds all `t:Explorer.Chain.Transaction.t/0`s given the address_hash and the token contract
address hash.
@ -2871,6 +2904,75 @@ defmodule Explorer.Chain do
value
end
@doc "Get staking pools from the DB"
@spec staking_pools(filter :: :validator | :active | :inactive, options :: PagingOptions.t()) :: [map()]
def staking_pools(filter, %PagingOptions{page_size: page_size, page_number: page_number} \\ @default_paging_options) do
off = page_size * (page_number - 1)
Address.Name
|> staking_pool_filter(filter)
|> limit(^page_size)
|> offset(^off)
|> Repo.all()
end
@doc "Get count of staking pools from the DB"
@spec staking_pools_count(filter :: :validator | :active | :inactive) :: integer
def staking_pools_count(filter) do
Address.Name
|> staking_pool_filter(filter)
|> Repo.aggregate(:count, :address_hash)
end
defp staking_pool_filter(query, :validator) do
where(
query,
[address],
fragment(
"""
(?->>'is_active')::boolean = true and
(?->>'deleted')::boolean is not true and
(?->>'is_validator')::boolean = true
""",
address.metadata,
address.metadata,
address.metadata
)
)
end
defp staking_pool_filter(query, :active) do
where(
query,
[address],
fragment(
"""
(?->>'is_active')::boolean = true and
(?->>'deleted')::boolean is not true
""",
address.metadata,
address.metadata
)
)
end
defp staking_pool_filter(query, :inactive) do
where(
query,
[address],
fragment(
"""
(?->>'is_active')::boolean = false and
(?->>'deleted')::boolean is not true
""",
address.metadata,
address.metadata
)
)
end
defp staking_pool_filter(query, _), do: query
defp with_decompiled_code_flag(query, hash) do
has_decompiled_code_query =
from(decompiled_contract in DecompiledSmartContract,

@ -41,6 +41,9 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do
|> Map.put(:timestamps, timestamps)
multi
|> Multi.run(:mark_as_deleted, fn repo, _ ->
mark_as_deleted(repo, changes_list, insert_options)
end)
|> Multi.run(:insert_staking_pools, fn repo, _ ->
insert(repo, changes_list, insert_options)
end)
@ -49,6 +52,32 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do
@impl Import.Runner
def timeout, do: @timeout
defp mark_as_deleted(repo, changes_list, %{timeout: timeout}) when is_list(changes_list) do
addresses = Enum.map(changes_list, & &1.address_hash)
query =
from(
address_name in Address.Name,
where:
address_name.address_hash not in ^addresses and
fragment("(?->>'is_pool')::boolean = true", address_name.metadata),
update: [
set: [
metadata: fragment("? || '{\"deleted\": true}'::jsonb", address_name.metadata)
]
]
)
try do
{_, result} = repo.update_all(query, [], timeout: timeout)
{:ok, result}
rescue
postgrex_error in Postgrex.Error ->
{:error, %{exception: postgrex_error}}
end
end
@spec insert(Repo.t(), [map()], %{
optional(:on_conflict) => Import.Runner.on_conflict(),
required(:timeout) => timeout,

@ -4,10 +4,11 @@ defmodule Explorer.PagingOptions do
number and index.
"""
@type t :: %__MODULE__{key: key, page_size: page_size}
@type t :: %__MODULE__{key: key, page_size: page_size, page_number: page_number}
@typep key :: any()
@typep page_size :: non_neg_integer()
@typep page_number :: pos_integer()
defstruct [:key, :page_size]
defstruct [:key, :page_size, page_number: 1]
end

@ -0,0 +1,124 @@
defmodule Explorer.Staking.EpochCounter do
@moduledoc """
Fetches current staking epoch number and the epoch end block number.
It subscribes to handle new blocks and conclude whether the epoch is over.
"""
use GenServer
alias Explorer.Chain.Events.Subscriber
alias Explorer.SmartContract.Reader
@table_name __MODULE__
@epoch_key "epoch_num"
@epoch_end_key "epoch_end_block"
@doc "Current staking epoch number"
def epoch_number do
if :ets.info(@table_name) != :undefined do
case :ets.lookup(@table_name, @epoch_key) do
[{_, epoch_num}] ->
epoch_num
_ ->
0
end
end
end
@doc "Block number on which will start new epoch"
def epoch_end_block do
if :ets.info(@table_name) != :undefined do
case :ets.lookup(@table_name, @epoch_end_key) do
[{_, epoch_end}] ->
epoch_end
_ ->
0
end
end
end
def start_link([]) do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end
def init([]) do
:ets.new(@table_name, [
:set,
:named_table,
:public,
write_concurrency: true
])
Subscriber.to(:blocks, :realtime)
{:ok, [], {:continue, :epoch_info}}
end
def handle_continue(:epoch_info, state) do
fetch_epoch_info()
{:noreply, state}
end
@doc "Handles new blocks and decides to fetch new epoch info"
def handle_info({:chain_event, :blocks, :realtime, blocks}, state) do
new_block_number =
blocks
|> Enum.map(&Map.get(&1, :number, 0))
|> Enum.max(fn -> 0 end)
case :ets.lookup(@table_name, @epoch_end_key) do
[] ->
fetch_epoch_info()
[{_, epoch_end_block}] when epoch_end_block < new_block_number ->
fetch_epoch_info()
_ ->
:ok
end
{:noreply, state}
end
defp fetch_epoch_info do
with data <- get_epoch_info(),
{:ok, [epoch_num]} <- data["stakingEpoch"],
{:ok, [epoch_end_block]} <- data["stakingEpochEndBlock"] do
:ets.insert(@table_name, {@epoch_key, epoch_num})
:ets.insert(@table_name, {@epoch_end_key, epoch_end_block})
end
end
defp get_epoch_info do
contract_abi = abi("staking.json")
functions = ["stakingEpoch", "stakingEpochEndBlock"]
functions
|> Enum.map(fn function ->
%{
contract_address: staking_address(),
function_name: function,
args: []
}
end)
|> Reader.query_contracts(contract_abi)
|> Enum.zip(functions)
|> Enum.into(%{}, fn {response, function} ->
{function, response}
end)
end
defp staking_address do
Application.get_env(:explorer, __MODULE__, [])[:staking_contract_address]
end
# sobelow_skip ["Traversal"]
defp abi(file_name) do
:explorer
|> Application.app_dir("priv/contracts_abi/pos/#{file_name}")
|> File.read!()
|> Jason.decode!()
end
end

@ -29,6 +29,7 @@ defmodule Explorer.Staking.PoolsReader do
{:ok, [delegator_addresses]} <- data["poolDelegators"],
delegators_count = Enum.count(delegator_addresses),
{:ok, [staked_amount]} <- data["stakeAmountTotalMinusOrderedWithdraw"],
{:ok, [self_staked_amount]} <- data["stakeAmountMinusOrderedWithdraw"],
{:ok, [is_validator]} <- data["isValidator"],
{:ok, [was_validator_count]} <- data["validatorCounter"],
{:ok, [is_banned]} <- data["isValidatorBanned"],
@ -42,6 +43,7 @@ defmodule Explorer.Staking.PoolsReader do
is_active: is_active,
delegators_count: delegators_count,
staked_amount: staked_amount,
self_staked_amount: self_staked_amount,
is_validator: is_validator,
was_validator_count: was_validator_count,
is_banned: is_banned,
@ -77,14 +79,15 @@ defmodule Explorer.Staking.PoolsReader do
contract_abi = abi("staking.json") ++ abi("validators.json")
methods = [
{:staking, "isPoolActive", staking_address},
{:staking, "poolDelegators", staking_address},
{:staking, "stakeAmountTotalMinusOrderedWithdraw", staking_address},
{:validators, "isValidator", mining_address},
{:validators, "validatorCounter", mining_address},
{:validators, "isValidatorBanned", mining_address},
{:validators, "bannedUntil", mining_address},
{:validators, "banCounter", mining_address}
{:staking, "isPoolActive", [staking_address]},
{:staking, "poolDelegators", [staking_address]},
{:staking, "stakeAmountTotalMinusOrderedWithdraw", [staking_address]},
{:staking, "stakeAmountMinusOrderedWithdraw", [staking_address, staking_address]},
{:validators, "isValidator", [mining_address]},
{:validators, "validatorCounter", [mining_address]},
{:validators, "isValidatorBanned", [mining_address]},
{:validators, "bannedUntil", [mining_address]},
{:validators, "banCounter", [mining_address]}
]
methods
@ -96,11 +99,11 @@ defmodule Explorer.Staking.PoolsReader do
end)
end
defp format_request({contract_name, function_name, param}) do
defp format_request({contract_name, function_name, params}) do
%{
contract_address: contract(contract_name),
function_name: function_name,
args: [param]
args: params
}
end

File diff suppressed because it is too large Load Diff

@ -50,6 +50,51 @@ defmodule Explorer.ChainTest do
end
end
describe "address_to_logs/2" do
test "fetches logs" 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)
assert Enum.count(Chain.address_to_logs(address)) == 2
end
test "paginates logs" do
address = insert(:address)
transaction =
:transaction
|> insert(to_address: address)
|> with_block()
log1 = insert(:log, transaction: transaction, index: 1, address: address)
2..51
|> Enum.map(fn index -> insert(:log, transaction: transaction, index: index, address: address) end)
|> Enum.map(& &1.index)
paging_options1 = %PagingOptions{page_size: 1}
[_log] = Chain.address_to_logs(address, paging_options: paging_options1)
paging_options2 = %PagingOptions{page_size: 60, key: {transaction.block_number, transaction.index, log1.index}}
assert Enum.count(Chain.address_to_logs(address, paging_options: paging_options2)) == 50
end
end
describe "address_to_transactions_with_rewards/2" do
test "without transactions" do
address = insert(:address)
@ -3903,4 +3948,59 @@ defmodule Explorer.ChainTest do
refute Chain.contract_address?(to_string(hash), 1, json_rpc_named_arguments)
end
end
describe "staking_pools/3" do
test "validators staking pools" do
inserted_validator = insert(:address_name, primary: true, metadata: %{is_active: true, is_validator: true})
insert(:address_name, primary: true, metadata: %{is_active: true, is_validator: false})
options = %PagingOptions{page_size: 20, page_number: 1}
assert [gotten_validator] = Chain.staking_pools(:validator, options)
assert inserted_validator.address_hash == gotten_validator.address_hash
end
test "active staking pools" do
inserted_validator = insert(:address_name, primary: true, metadata: %{is_active: true})
insert(:address_name, primary: true, metadata: %{is_active: false})
options = %PagingOptions{page_size: 20, page_number: 1}
assert [gotten_validator] = Chain.staking_pools(:active, options)
assert inserted_validator.address_hash == gotten_validator.address_hash
end
test "inactive staking pools" do
insert(:address_name, primary: true, metadata: %{is_active: true})
inserted_validator = insert(:address_name, primary: true, metadata: %{is_active: false})
options = %PagingOptions{page_size: 20, page_number: 1}
assert [gotten_validator] = Chain.staking_pools(:inactive, options)
assert inserted_validator.address_hash == gotten_validator.address_hash
end
end
describe "staking_pools_count/1" do
test "validators staking pools" do
insert(:address_name, primary: true, metadata: %{is_active: true, is_validator: true})
insert(:address_name, primary: true, metadata: %{is_active: true, is_validator: false})
assert Chain.staking_pools_count(:validator) == 1
end
test "active staking pools" do
insert(:address_name, primary: true, metadata: %{is_active: true})
insert(:address_name, primary: true, metadata: %{is_active: false})
assert Chain.staking_pools_count(:active) == 1
end
test "inactive staking pools" do
insert(:address_name, primary: true, metadata: %{is_active: true})
insert(:address_name, primary: true, metadata: %{is_active: false})
assert Chain.staking_pools_count(:inactive) == 1
end
end
end

@ -0,0 +1,97 @@
defmodule Explorer.Staking.EpochCounterTest do
use ExUnit.Case, async: false
import Mox
alias Explorer.Staking.EpochCounter
alias Explorer.Chain.Events.Publisher
setup :verify_on_exit!
setup :set_mox_global
test "when disabled, it returns nil" do
assert EpochCounter.epoch_number() == nil
assert EpochCounter.epoch_end_block() == nil
end
test "fetch epoch data" do
set_mox(10, 880)
Application.put_env(:explorer, EpochCounter, enabled: true)
start_supervised!(EpochCounter)
Process.sleep(1_000)
assert EpochCounter.epoch_number() == 10
assert EpochCounter.epoch_end_block() == 880
end
test "fetch new epoch data" do
set_mox(10, 880)
Application.put_env(:explorer, EpochCounter, enabled: true)
start_supervised!(EpochCounter)
Process.sleep(1_000)
assert EpochCounter.epoch_number() == 10
assert EpochCounter.epoch_end_block() == 880
event_type = :blocks
broadcast_type = :realtime
event_data = [%Explorer.Chain.Block{number: 881}]
set_mox(11, 960)
Publisher.broadcast([{event_type, event_data}], broadcast_type)
Process.sleep(1_000)
assert EpochCounter.epoch_number() == 11
assert EpochCounter.epoch_end_block() == 960
end
defp set_mox(epoch_num, end_block_num) do
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn [
%{
id: 0,
jsonrpc: "2.0",
method: "eth_call",
params: _
},
%{
id: 1,
jsonrpc: "2.0",
method: "eth_call",
params: _
}
],
_options ->
{:ok,
[
%{
id: 0,
jsonrpc: "2.0",
result: encode_num(epoch_num)
},
%{
id: 1,
jsonrpc: "2.0",
result: encode_num(end_block_num)
}
]}
end
)
end
defp encode_num(num) do
selector = %ABI.FunctionSelector{function: nil, types: [uint: 32]}
encoded_num =
[num]
|> ABI.TypeEncoder.encode(selector)
|> Base.encode16(case: :lower)
"0x" <> encoded_num
end
end

@ -1,6 +1,5 @@
defmodule Explorer.Token.PoolsReaderTest do
use EthereumJSONRPC.Case
use Explorer.DataCase
alias Explorer.Staking.PoolsReader
@ -44,6 +43,7 @@ defmodule Explorer.Token.PoolsReaderTest do
mining_address:
<<187, 202, 168, 212, 130, 137, 187, 31, 252, 249, 128, 141, 154, 164, 177, 210, 21, 5, 76, 120>>,
staked_amount: 0,
self_staked_amount: 0,
staking_address: <<11, 47, 94, 47, 60, 189, 134, 78, 170, 44, 100, 46, 55, 105, 193, 88, 35, 97, 202, 246>>,
was_banned_count: 0,
was_validator_count: 2
@ -162,6 +162,25 @@ defmodule Explorer.Token.PoolsReaderTest do
result: "0x0000000000000000000000000000000000000000000000000000000000000000"
}
# stakeAmountMinusOrderedWithdraw
%{
id: id,
jsonrpc: "2.0",
method: "eth_call",
params: [
%{
data:
"0x58daab6a0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf60000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6",
to: _
},
"latest"
]
} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000000000000000000"
}
# isValidator
%{
id: id,

@ -125,6 +125,7 @@ defmodule Indexer.Fetcher.StakingPools do
pool
|> Map.delete(:staking_address)
|> Map.put(:mining_address, mining_address)
|> Map.put(:is_pool, true)
%{
name: "anonymous",

@ -129,6 +129,25 @@ defmodule Indexer.Fetcher.StakingPoolsTest do
result: "0x0000000000000000000000000000000000000000000000000000000000000000"
}
# stakeAmountMinusOrderedWithdraw
%{
id: id,
jsonrpc: "2.0",
method: "eth_call",
params: [
%{
data:
"0x58daab6a0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf60000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6",
to: _
},
"latest"
]
} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000000000000000000"
}
# isValidator
%{
id: id,

Loading…
Cancel
Save