Merge branch 'mobile-menu' of github.com:YegorSan/blockscout into mobile-menu

pull/2665/head
Yegor San 5 years ago
commit 8e66349ace
  1. 3
      CHANGELOG.md
  2. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex
  3. 9
      apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex
  4. 20
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs
  5. 47
      apps/block_scout_web/test/block_scout_web/views/address_contract_view_test.exs
  6. 19
      apps/explorer/config/config.exs
  7. 4
      apps/explorer/lib/explorer/application.ex
  8. 22
      apps/explorer/lib/explorer/chain.ex
  9. 4
      apps/explorer/lib/explorer/chain/cache/block_number.ex
  10. 4
      apps/explorer/lib/explorer/chain/cache/blocks.ex
  11. 4
      apps/explorer/lib/explorer/chain/cache/transactions.ex
  12. 42
      apps/explorer/lib/explorer/chain/ordered_cache.ex
  13. 39
      apps/explorer/lib/explorer/chain/smart_contract.ex
  14. 54
      apps/explorer/lib/explorer/chain_spec/parity/importer.ex
  15. 89
      apps/explorer/lib/explorer/chain_spec/poa/importer.ex
  16. 6
      apps/explorer/test/explorer/chain/cache/block_number_test.exs
  17. 2
      docs/env-variables.md

@ -2,6 +2,8 @@
### Features
- [#2665](https://github.com/poanetwork/blockscout/pull/2665) - New layout for mobile menu
- [#2588](https://github.com/poanetwork/blockscout/pull/2588) - add verification submission comment
- [#2505](https://github.com/poanetwork/blockscout/pull/2505) - support POA Network emission rewards
- [#2636](https://github.com/poanetwork/blockscout/pull/2636) - Execute all address' transactions page queries in parallel
- [#2596](https://github.com/poanetwork/blockscout/pull/2596) - support AuRa's empty step reward type
- [#2581](https://github.com/poanetwork/blockscout/pull/2581) - Add generic Map-like Cache behaviour and implementation
@ -11,6 +13,7 @@
- [#2497](https://github.com/poanetwork/blockscout/pull/2497) - Add generic Ordered Cache behaviour and implementation
### Fixes
- [#2612](https://github.com/poanetwork/blockscout/pull/2612) - Add cache updating independently from Indexer
- [#2659](https://github.com/poanetwork/blockscout/pull/2659) - Multipurpose front-end part update
- [#2468](https://github.com/poanetwork/blockscout/pull/2468) - fix confirmations for non consensus blocks
- [#2610](https://github.com/poanetwork/blockscout/pull/2610) - use CoinGecko instead of CoinMarketcap for exchange rates

@ -66,7 +66,7 @@
</button>
</div>
<div class="tile tile-muted mb-4">
<pre class="pre-scrollable line-numbers" data-activate-highlight><code class="solidity"><%= for {line, number} <- contract_lines_with_index(@address.smart_contract.contract_source_code) do %><div data-line-number="<%= number %>"><%= line %></div><% end %></code></pre>
<pre class="pre-scrollable line-numbers" data-activate-highlight><code class="solidity"><%= for {line, number} <- contract_lines_with_index(@address.smart_contract.contract_source_code, @address.smart_contract.inserted_at) do %><div data-line-number="<%= number %>"><%= line %></div><% end %></code></pre>
</div>
</section>

@ -2,7 +2,7 @@ defmodule BlockScoutWeb.AddressContractView do
use BlockScoutWeb, :view
alias ABI.{FunctionSelector, TypeDecoder}
alias Explorer.Chain.{Address, Data, InternalTransaction}
alias Explorer.Chain.{Address, Data, InternalTransaction, SmartContract}
def render("scripts.html", %{conn: conn}) do
render_scripts(conn, "address_contract/code_highlighting.js")
@ -63,8 +63,11 @@ defmodule BlockScoutWeb.AddressContractView do
end)
end
def contract_lines_with_index(contract_source_code) do
contract_lines = String.split(contract_source_code, "\n")
def contract_lines_with_index(source_code, inserted_at \\ nil) do
contract_lines =
source_code
|> String.split("\n")
|> SmartContract.add_submitted_comment(inserted_at)
max_digits =
contract_lines

@ -1,6 +1,6 @@
defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
use BlockScoutWeb.ConnCase
alias Explorer.Factory
alias Explorer.{Chain, Factory}
describe "listcontracts" do
setup do
@ -450,7 +450,9 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
expected_result = [
%{
"Address" => to_string(contract.address_hash),
"SourceCode" => contract.contract_source_code,
"SourceCode" =>
"/**\n* Submitted for verification at blockscout.com on #{contract.inserted_at}\n*/\n" <>
contract.contract_source_code,
"ABI" => Jason.encode!(contract.abi),
"ContractName" => contract.name,
"CompilerVersion" => contract.compiler_version,
@ -496,9 +498,13 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
|> get("/api", params)
|> json_response(200)
verified_contract = Chain.address_hash_to_smart_contract(contract_address.hash)
expected_result = %{
"Address" => to_string(contract_address.hash),
"SourceCode" => contract_code_info.source_code,
"SourceCode" =>
"/**\n* Submitted for verification at blockscout.com on #{verified_contract.inserted_at}\n*/\n" <>
contract_code_info.source_code,
"ABI" => Jason.encode!(contract_code_info.abi),
"ContractName" => contract_code_info.name,
"CompilerVersion" => contract_code_info.version,
@ -563,8 +569,14 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
result = response["result"]
verified_contract = Chain.address_hash_to_smart_contract(contract_address.hash)
assert result["Address"] == to_string(contract_address.hash)
assert result["SourceCode"] == contract_source_code
assert result["SourceCode"] ==
"/**\n* Submitted for verification at blockscout.com on #{verified_contract.inserted_at}\n*/\n" <>
contract_source_code
assert result["ContractName"] == name
assert result["DecompiledSourceCode"] == "Contract source code not decompiled."
assert result["DecompilerVersion"] == ""

@ -38,22 +38,25 @@ defmodule BlockScoutWeb.AddressContractViewTest do
result = AddressContractView.contract_lines_with_index(code)
assert result == [
{"pragma solidity >=0.4.22 <0.6.0;", " 1"},
{"", " 2"},
{"struct Proposal {", " 3"},
{" uint voteCount;", " 4"},
{"}", " 5"},
{"", " 6"},
{"address chairperson;", " 7"},
{"mapping(address => Voter) voters;", " 8"},
{"Proposal[] proposals;", " 9"},
{"", "10"},
{"constructor(uint8 _numProposals) public {", "11"},
{" chairperson = msg.sender;", "12"},
{" voters[chairperson].weight = 1;", "13"},
{" proposals.length = _numProposals;", "14"},
{"}", "15"},
{"", "16"}
{"/**", " 1"},
{"* Submitted for verification at blockscout.com on ", " 2"},
{"*/", " 3"},
{"pragma solidity >=0.4.22 <0.6.0;", " 4"},
{"", " 5"},
{"struct Proposal {", " 6"},
{" uint voteCount;", " 7"},
{"}", " 8"},
{"", " 9"},
{"address chairperson;", "10"},
{"mapping(address => Voter) voters;", "11"},
{"Proposal[] proposals;", "12"},
{"", "13"},
{"constructor(uint8 _numProposals) public {", "14"},
{" chairperson = msg.sender;", "15"},
{" voters[chairperson].weight = 1;", "16"},
{" proposals.length = _numProposals;", "17"},
{"}", "18"},
{"", "19"}
]
end
@ -66,7 +69,17 @@ defmodule BlockScoutWeb.AddressContractViewTest do
test "returns a list of tuples and the first element is just a line from the original string" do
result = AddressContractView.contract_lines_with_index("a\nb\nc\nd\ne")
assert Enum.map(result, fn {line, _number} -> line end) == ["a", "b", "c", "d", "e"]
assert Enum.map(result, fn {line, _number} -> line end) == [
"/**",
"* Submitted for verification at blockscout.com on ",
"*/",
"a",
"b",
"c",
"d",
"e"
]
end
end
end

@ -27,9 +27,16 @@ config :explorer, Explorer.Counters.AverageBlockTime,
enabled: true,
period: average_block_period
config :explorer, Explorer.ChainSpec.GenesisData, enabled: false, chain_spec_path: System.get_env("CHAIN_SPEC_PATH")
config :explorer, Explorer.ChainSpec.GenesisData,
enabled: true,
chain_spec_path: System.get_env("CHAIN_SPEC_PATH"),
emission_format: System.get_env("EMISSION_FORMAT", "DEFAULT"),
rewards_contract_address: System.get_env("REWARDS_CONTRACT_ADDRESS", "0xeca443e8e1ab29971a45a9c57a6a9875701698a5")
config :explorer, Explorer.Chain.Cache.BlockNumber, enabled: true
config :explorer, Explorer.Chain.Cache.BlockNumber,
enabled: true,
ttl_check_interval: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(1), else: false),
global_ttl: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(5))
config :explorer, Explorer.ExchangeRates.Source.CoinGecko, coin_id: System.get_env("COIN_GECKO_ID", "poa-network")
@ -123,6 +130,14 @@ market_history_cache_period =
config :explorer, Explorer.Market.MarketHistoryCache, period: market_history_cache_period
config :explorer, Explorer.Chain.Cache.Blocks,
ttl_check_interval: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(1), else: false),
global_ttl: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(5))
config :explorer, Explorer.Chain.Cache.Transactions,
ttl_check_interval: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(1), else: false),
global_ttl: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(5))
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs"

@ -56,9 +56,7 @@ defmodule Explorer.Application do
opts = [strategy: :one_for_one, name: Explorer.Supervisor]
res = Supervisor.start_link(children, opts)
res
Supervisor.start_link(children, opts)
end
defp configurable_children do

@ -2592,8 +2592,26 @@ defmodule Explorer.Chain do
@spec address_hash_to_address_with_source_code(Hash.Address.t()) :: Address.t() | nil
def address_hash_to_address_with_source_code(address_hash) do
case Repo.get(Address, address_hash) do
nil -> nil
address -> Repo.preload(address, [:smart_contract, :decompiled_smart_contracts])
nil ->
nil
address ->
address_with_smart_contract = Repo.preload(address, [:smart_contract, :decompiled_smart_contracts])
if address_with_smart_contract.smart_contract do
formatted_code =
SmartContract.add_submitted_comment(
address_with_smart_contract.smart_contract.contract_source_code,
address_with_smart_contract.smart_contract.inserted_at
)
%{
address_with_smart_contract
| smart_contract: %{address_with_smart_contract.smart_contract | contract_source_code: formatted_code}
}
else
address_with_smart_contract
end
end
end

@ -7,7 +7,9 @@ defmodule Explorer.Chain.Cache.BlockNumber do
use Explorer.Chain.MapCache,
name: :block_number,
keys: [:min, :max]
keys: [:min, :max],
ttl_check_interval: Application.get_env(:explorer, __MODULE__)[:ttl_check_interval],
global_ttl: Application.get_env(:explorer, __MODULE__)[:global_ttl]
alias Explorer.Chain

@ -11,7 +11,9 @@ defmodule Explorer.Chain.Cache.Blocks do
ids_list_key: "block_numbers",
preload: :transactions,
preload: [miner: :names],
preload: :rewards
preload: :rewards,
ttl_check_interval: Application.get_env(:explorer, __MODULE__)[:ttl_check_interval],
global_ttl: Application.get_env(:explorer, __MODULE__)[:global_ttl]
@type element :: Block.t()

@ -16,7 +16,9 @@ defmodule Explorer.Chain.Cache.Transactions do
token_transfers: :token,
token_transfers: :from_address,
token_transfers: :to_address
]
],
ttl_check_interval: Application.get_env(:explorer, __MODULE__)[:ttl_check_interval],
global_ttl: Application.get_env(:explorer, __MODULE__)[:global_ttl]
@type element :: Transaction.t()

@ -26,7 +26,10 @@ defmodule Explorer.Chain.OrderedCache do
preload: [transaction: :hash]
```
Additionally all of the options accepted by `ConCache.start_link/1` can be
provided as well. By default only `ttl_check_interval:` is set (to `false`).
provided as well. Unless specified, only these values have defaults:
- `:ttl_check_interval` is set (to `false`).
- `:callback` is only set if `:ttl_check_interval` is not `false` to call the
`remove_deleted_from_index` function, that removes expired values from the index.
It's also possible, and advised, to override the implementation of the `c:prevails?/2`
and `c:element_to_id/1` callbacks.
@ -131,10 +134,7 @@ defmodule Explorer.Chain.OrderedCache do
max_size = Keyword.get(opts, :max_size, 100)
preloads = Keyword.get(opts, :preloads) || Keyword.get_values(opts, :preload)
concache_params =
opts
|> Keyword.drop([:ids_list_key, :max_size, :preloads, :preload])
|> Keyword.put_new(:ttl_check_interval, false)
concache_params = Keyword.drop(opts, [:ids_list_key, :max_size, :preloads, :preload])
# credo:disable-for-next-line Credo.Check.Refactor.LongQuoteBlocks
quote do
@ -206,6 +206,19 @@ defmodule Explorer.Chain.OrderedCache do
### Updating function
def remove_deleted_from_index({:delete, _cache_pid, id}) do
# simply check with `ConCache.get` because it is faster
if Enum.member?(ids_list(), id) do
ConCache.update(cache_name(), ids_list_key(), fn ids ->
updated_list = List.delete(ids || [], id)
# ids_list is set to never expire
{:ok, %ConCache.Item{value: updated_list, ttl: :infinity}}
end)
end
end
def remove_deleted_from_index(_), do: nil
@impl OrderedCache
def update(elements) when is_nil(elements), do: :ok
@ -217,7 +230,8 @@ defmodule Explorer.Chain.OrderedCache do
|> Enum.sort(&prevails?(&1, &2))
|> merge_and_update(ids || [], max_size())
{:ok, updated_list}
# ids_list is set to never expire
{:ok, %ConCache.Item{value: updated_list, ttl: :infinity}}
end)
end
@ -308,7 +322,21 @@ defmodule Explorer.Chain.OrderedCache do
provided to this function will override the ones set by using the macro
"""
def child_spec(params) do
params = Keyword.merge(unquote(concache_params), params)
# params specified in `use`
merged_params =
unquote(concache_params)
# params specified in `child_spec`
|> Keyword.merge(params)
# `:ttl_check_interval` needs to be specified, defaults to `false`
|> Keyword.put_new(:ttl_check_interval, false)
# if `:ttl_check_interval` is not `false` the expired values need to be
# removed from the cache's index
params =
case merged_params[:ttl_check_interval] do
false -> merged_params
_ -> Keyword.put_new(merged_params, :callback, &remove_deleted_from_index/1)
end
Supervisor.child_spec({ConCache, params}, id: child_id())
end

@ -271,6 +271,45 @@ defmodule Explorer.Chain.SmartContract do
|> add_error(:contract_source_code, error_message(error))
end
def add_submitted_comment(code, inserted_at) when is_binary(code) do
code
|> String.split("\n")
|> add_submitted_comment(inserted_at)
|> Enum.join("\n")
end
def add_submitted_comment(contract_lines, inserted_at) when is_list(contract_lines) do
etherscan_index =
Enum.find_index(contract_lines, fn line ->
String.contains?(line, "Submitted for verification at Etherscan.io")
end)
blockscout_index =
Enum.find_index(contract_lines, fn line ->
String.contains?(line, "Submitted for verification at blockscout.com")
end)
cond do
etherscan_index && blockscout_index ->
List.replace_at(contract_lines, etherscan_index, "*")
etherscan_index && !blockscout_index ->
List.replace_at(
contract_lines,
etherscan_index,
"* Submitted for verification at blockscout.com on #{inserted_at}"
)
!etherscan_index && !blockscout_index ->
header = ["/**", "* Submitted for verification at blockscout.com on #{inserted_at}", "*/"]
header ++ contract_lines
true ->
contract_lines
end
end
defp upsert_contract_methods(%Ecto.Changeset{changes: %{abi: abi}} = changeset) do
ContractMethod.upsert_from_abi(abi, get_field(changeset, :address_hash))

@ -3,18 +3,23 @@ defmodule Explorer.ChainSpec.Parity.Importer do
Imports data from parity chain spec.
"""
require Logger
alias Explorer.{Chain, Repo}
alias Explorer.Chain.Block.{EmissionReward, Range}
alias Explorer.Chain.Hash.Address, as: AddressHash
alias Explorer.Chain.Wei
alias Explorer.ChainSpec.GenesisData
alias Explorer.ChainSpec.POA.Importer, as: PoaEmissionImporter
@max_block_number :infinity
def import_emission_rewards(chain_spec) do
rewards = emission_rewards(chain_spec)
{_, nil} = Repo.delete_all(EmissionReward)
{_, nil} = Repo.insert_all(EmissionReward, rewards)
if Application.get_env(:explorer, GenesisData)[:emission_format] == "POA" do
PoaEmissionImporter.import_emission_rewards()
else
import_rewards_from_chain_spec(chain_spec)
end
end
def import_genesis_coin_balances(chain_spec) do
@ -38,18 +43,37 @@ defmodule Explorer.ChainSpec.Parity.Importer do
Chain.import(params)
end
defp import_rewards_from_chain_spec(chain_spec) do
rewards = emission_rewards(chain_spec)
{_, nil} = Repo.delete_all(EmissionReward)
{_, nil} = Repo.insert_all(EmissionReward, rewards)
end
def genesis_coin_balances(chain_spec) do
accounts = chain_spec["accounts"]
parse_accounts(accounts)
if accounts do
parse_accounts(accounts)
else
Logger.warn(fn -> "No accounts are defined in chain spec" end)
[]
end
end
def emission_rewards(chain_spec) do
rewards = chain_spec["engine"]["Ethash"]["params"]["blockReward"]
rewards
|> parse_hex_numbers()
|> format_ranges()
if rewards do
rewards
|> parse_hex_numbers()
|> format_ranges()
else
Logger.warn(fn -> "No rewards are defined in chain spec" end)
[]
end
end
defp parse_accounts(accounts) do
@ -59,7 +83,7 @@ defmodule Explorer.ChainSpec.Parity.Importer do
end)
|> Stream.map(fn {address, %{"balance" => value}} ->
{:ok, address_hash} = AddressHash.cast(address)
balance = parse_hex_number(value)
balance = parse_number(value)
%{address_hash: address_hash, value: balance}
end)
@ -92,16 +116,22 @@ defmodule Explorer.ChainSpec.Parity.Importer do
defp parse_hex_numbers(rewards) do
Enum.map(rewards, fn {hex_block_number, hex_reward} ->
block_number = parse_hex_number(hex_block_number)
{:ok, reward} = hex_reward |> parse_hex_number() |> Wei.cast()
block_number = parse_number(hex_block_number)
{:ok, reward} = hex_reward |> parse_number() |> Wei.cast()
{block_number, reward}
end)
end
defp parse_hex_number("0x" <> hex_number) do
defp parse_number("0x" <> hex_number) do
{number, ""} = Integer.parse(hex_number, 16)
number
end
defp parse_number(string_number) do
{number, ""} = Integer.parse(string_number, 10)
number
end
end

@ -0,0 +1,89 @@
defmodule Explorer.ChainSpec.POA.Importer do
@moduledoc """
Imports emission reward range for POA chain.
"""
require Logger
alias Explorer.Chain.Wei
alias Explorer.Repo
alias Explorer.SmartContract.Reader
alias Explorer.Chain.Block.{EmissionReward, Range}
alias Explorer.ChainSpec.GenesisData
@block_reward_amount_abi %{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "uint256", "name" => ""}],
"name" => "blockRewardAmount",
"inputs" => [],
"constant" => true
}
@block_reward_amount_params %{"blockRewardAmount" => []}
@emission_funds_amount_abi %{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "uint256", "name" => ""}],
"name" => "emissionFundsAmount",
"inputs" => [],
"constant" => true
}
@emission_funds_amount_params %{"emissionFundsAmount" => []}
@emission_funds_block_start 5_098_087
def import_emission_rewards do
if is_nil(rewards_contract_address()) do
Logger.warn(fn -> "No rewards contract address is defined" end)
else
block_reward = block_reward_amount()
emission_funds = emission_funds_amount()
rewards = [
%{
block_range: %Range{from: 0, to: @emission_funds_block_start},
reward: %Wei{value: block_reward}
},
%{
block_range: %Range{from: @emission_funds_block_start + 1, to: :infinity},
reward: %Wei{value: Decimal.add(block_reward, emission_funds)}
}
]
{_, nil} = Repo.delete_all(EmissionReward)
{_, nil} = Repo.insert_all(EmissionReward, rewards)
end
end
def block_reward_amount do
call_contract(rewards_contract_address(), @block_reward_amount_abi, @block_reward_amount_params)
end
def emission_funds_amount do
call_contract(rewards_contract_address(), @emission_funds_amount_abi, @emission_funds_amount_params)
end
defp rewards_contract_address do
Application.get_env(:explorer, GenesisData)[:rewards_contract_address]
end
defp call_contract(address, abi, params) do
abi = [abi]
method_name =
params
|> Enum.map(fn {key, _value} -> key end)
|> List.first()
Reader.query_contract(address, abi, params)
value =
case Reader.query_contract(address, abi, params) do
%{^method_name => {:ok, [result]}} -> result
_ -> 0
end
Decimal.new(value)
end
end

@ -11,7 +11,7 @@ defmodule Explorer.Chain.Cache.BlockNumberTest do
end)
end
describe "get_max/1" do
describe "get_max/0" do
test "returns max number" do
insert(:block, number: 5)
@ -19,7 +19,7 @@ defmodule Explorer.Chain.Cache.BlockNumberTest do
end
end
describe "get_min/1" do
describe "get_min/0" do
test "returns min number" do
insert(:block, number: 2)
@ -27,7 +27,7 @@ defmodule Explorer.Chain.Cache.BlockNumberTest do
end
end
describe "get_all/1" do
describe "get_all/0" do
test "returns min and max number" do
insert(:block, number: 6)

@ -66,3 +66,5 @@ $ export NETWORK=POA
| `API_URL` | | Link to API instance, e.g. `http://host/path` | (empty) | v2.0.3+ | | |
| `CHAIN_SPEC_PATH` | | Chain specification path (absolute file system path or url) to import block emission reward ranges and genesis account balances from | (empty) | master | | |
| `COIN_GECKO_ID` | | CoinGecko coin id required for fetching an exchange rate | poa-network | master | | |
| `EMISSION_FORMAT` | | Should be set to `POA` if you have block emission indentical to POA Network. This env var is used only if `CHAIN_SPEC_PATH` is set | `STANDARD` | master | | |
| `REWARDS_CONTRACT_ADDRESS` | | Emission rewards contract address. This env var is used only if `EMISSION_FORMAT` is set to `POA` | `0xeca443e8e1ab29971a45a9c57a6a9875701698a5` | master | | |

Loading…
Cancel
Save