Merge branch 'master' into ab-fix-reorg-uncles-pagination

pull/1905/head
Ayrat Badykov 6 years ago committed by GitHub
commit 4e6ab7da50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      CHANGELOG.md
  2. 4
      apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex
  3. 3
      apps/explorer/config/config.exs
  4. 4
      apps/explorer/lib/explorer/application.ex
  5. 17
      apps/explorer/lib/explorer/chain.ex
  6. 147
      apps/explorer/lib/explorer/chain/block_count_cache.ex
  7. 61
      apps/explorer/lib/explorer/smart_contract/verifier.ex
  8. 57
      apps/explorer/test/explorer/chain/block_count_cache_test.exs

@ -11,6 +11,7 @@
- [#1806](https://github.com/poanetwork/blockscout/pull/1806) - verify contracts with a post request - [#1806](https://github.com/poanetwork/blockscout/pull/1806) - verify contracts with a post request
- [#1857](https://github.com/poanetwork/blockscout/pull/1857) - Re-implement Geth JS internal transaction tracer in Elixir - [#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
- [#1876](https://github.com/poanetwork/blockscout/pull/1876) - async calculate a count of blocks
### Fixes ### Fixes
@ -29,6 +30,7 @@
- [#1875](https://github.com/poanetwork/blockscout/pull/1875) - fix: resolve false positive constructor arguments - [#1875](https://github.com/poanetwork/blockscout/pull/1875) - fix: resolve false positive constructor arguments
- [#1904](https://github.com/poanetwork/blockscout/pull/1904) - fix `BLOCK_COUNT_CACHE_TTL` env var type - [#1904](https://github.com/poanetwork/blockscout/pull/1904) - fix `BLOCK_COUNT_CACHE_TTL` env var type
- [#1898](https://github.com/poanetwork/blockscout/pull/1898) - check if the constructor has arguments before verifying constructor arguments - [#1898](https://github.com/poanetwork/blockscout/pull/1898) - check if the constructor has arguments before verifying constructor arguments
- [#1915](https://github.com/poanetwork/blockscout/pull/1915) - fallback to 2 latest evm versions
### Chore ### Chore

@ -3,7 +3,7 @@ defmodule BlockScoutWeb.ChainController do
alias BlockScoutWeb.ChainView alias BlockScoutWeb.ChainView
alias Explorer.{Chain, PagingOptions, Repo} alias Explorer.{Chain, PagingOptions, Repo}
alias Explorer.Chain.{Address, Block, BlockCountCache, Transaction} alias Explorer.Chain.{Address, Block, Transaction}
alias Explorer.Counters.AverageBlockTime alias Explorer.Counters.AverageBlockTime
alias Explorer.ExchangeRates.Token alias Explorer.ExchangeRates.Token
alias Explorer.Market alias Explorer.Market
@ -11,7 +11,7 @@ defmodule BlockScoutWeb.ChainController do
def show(conn, _params) do def show(conn, _params) do
transaction_estimated_count = Chain.transaction_estimated_count() transaction_estimated_count = Chain.transaction_estimated_count()
block_count = BlockCountCache.count() block_count = Chain.block_estimated_count()
exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null() exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null()

@ -82,9 +82,6 @@ config :spandex_ecto, SpandexEcto.EctoLogger,
tracer: Explorer.Tracer, tracer: Explorer.Tracer,
otp_app: :explorer otp_app: :explorer
config :explorer, Explorer.Chain.BlockCountCache,
ttl: System.get_env("BLOCK_COUNT_CACHE_TTL") && String.to_integer(System.get_env("BLOCK_COUNT_CACHE_TTL"))
# Import environment specific config. This must remain at the bottom # Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above. # of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs" import_config "#{Mix.env()}.exs"

@ -29,7 +29,8 @@ defmodule Explorer.Application do
Explorer.SmartContract.SolcDownloader, Explorer.SmartContract.SolcDownloader,
{Registry, keys: :duplicate, name: Registry.ChainEvents, id: Registry.ChainEvents}, {Registry, keys: :duplicate, name: Registry.ChainEvents, id: Registry.ChainEvents},
{Admin.Recovery, [[], [name: Admin.Recovery]]}, {Admin.Recovery, [[], [name: Admin.Recovery]]},
{TransactionCountCache, [[], []]} {TransactionCountCache, [[], []]},
{BlockCountCache, []}
] ]
children = base_children ++ configurable_children() children = base_children ++ configurable_children()
@ -39,7 +40,6 @@ defmodule Explorer.Application do
res = Supervisor.start_link(children, opts) res = Supervisor.start_link(children, opts)
BlockNumberCache.setup() BlockNumberCache.setup()
BlockCountCache.setup()
res res
end end

@ -29,6 +29,7 @@ defmodule Explorer.Chain do
Address.CurrentTokenBalance, Address.CurrentTokenBalance,
Address.TokenBalance, Address.TokenBalance,
Block, Block,
BlockCountCache,
BlockNumberCache, BlockNumberCache,
Data, Data,
DecompiledSmartContract, DecompiledSmartContract,
@ -1953,6 +1954,22 @@ defmodule Explorer.Chain do
end end
end end
@doc """
Estimated count of `t:Explorer.Chain.Block.t/0`.
Estimated count of consensus blocks.
"""
@spec block_estimated_count() :: non_neg_integer()
def block_estimated_count do
cached_value = BlockCountCache.count()
if is_nil(cached_value) do
block_consensus_count()
else
cached_value
end
end
@doc """ @doc """
`t:Explorer.Chain.InternalTransaction/0`s in `t:Explorer.Chain.Transaction.t/0` with `hash`. `t:Explorer.Chain.InternalTransaction/0`s in `t:Explorer.Chain.Transaction.t/0` with `hash`.

@ -1,79 +1,129 @@
defmodule Explorer.Chain.BlockCountCache do defmodule Explorer.Chain.BlockCountCache do
@moduledoc """ @moduledoc """
Cache for count consensus blocks. Cache for block count.
""" """
require Logger
use GenServer
alias Explorer.Chain alias Explorer.Chain
@tab :block_count_cache
# 10 minutes # 10 minutes
@cache_period 1_000 * 60 * 10 @cache_period 1_000 * 60 * 10
@key "count" @key "count"
@opts_key "opts" @default_value nil
@name __MODULE__
@spec setup() :: :ok def start_link(params) do
def setup do name = params[:name] || @name
if :ets.whereis(@tab) == :undefined do params_with_name = Keyword.put(params, :name, name)
:ets.new(@tab, [
:set, GenServer.start_link(__MODULE__, params_with_name, name: name)
:named_table,
:public,
write_concurrency: true
])
end end
setup_opts() def init(params) do
update_cache() cache_period = period_from_env_var() || params[:cache_period] || @cache_period
current_value = params[:default_value] || @default_value
name = params[:name]
:ok init_ets_table(name)
{:ok, {{cache_period, current_value, name}, nil}}
end end
def count do def count(process_name \\ __MODULE__) do
initial_cache = {_count, old_current_time} = cached_values() GenServer.call(process_name, :count)
end
{count, _current_time} = def handle_call(:count, _, {{cache_period, default_value, name}, task}) do
if current_time() - old_current_time > cache_period() do {count, task} =
update_cache() case cached_values(name) do
nil ->
{default_value, update_cache(task, name)}
cached_values() {cached_value, timestamp} ->
else task =
initial_cache if current_time() - timestamp > cache_period do
update_cache(task, name)
end end
count {cached_value, task}
end end
defp update_cache do {:reply, count, {{cache_period, default_value, name}, task}}
end
def update_cache(nil, name) do
async_update_cache(name)
end
def update_cache(task, _) do
task
end
def handle_cast({:update_cache, value}, {{cache_period, default_value, name}, _}) do
current_time = current_time() current_time = current_time()
count = count_from_db() tuple = {value, current_time}
tuple = {count, current_time}
table_name = table_name(name)
:ets.insert(table_name, {@key, tuple})
{:noreply, {{cache_period, default_value, name}, nil}}
end
:ets.insert(@tab, {@key, tuple}) def handle_info({:DOWN, _, _, _, _}, {{cache_period, default_value, name}, _}) do
{:noreply, {{cache_period, default_value, name}, nil}}
end end
defp setup_opts do def handle_info(_, {{cache_period, default_value, name}, _}) do
cache_period = Application.get_env(:explorer, __MODULE__)[:ttl] || @cache_period {:noreply, {{cache_period, default_value, name}, nil}}
end
:ets.insert(@tab, {@opts_key, cache_period}) # sobelow_skip ["DOS"]
defp table_name(name) do
name
|> Atom.to_string()
|> Macro.underscore()
|> String.to_atom()
end end
defp cached_values do def async_update_cache(name) do
[{_, cached_values}] = :ets.lookup(@tab, @key) Task.async(fn ->
try do
result = Chain.fetch_count_consensus_block()
cached_values GenServer.cast(name, {:update_cache, result})
rescue
e ->
Logger.debug([
"Coudn't update block count test #{inspect(e)}"
])
end
end)
end end
defp cache_period do defp init_ets_table(name) do
[{_, cache_period}] = :ets.lookup(@tab, @opts_key) table_name = table_name(name)
cache_period if :ets.whereis(table_name) == :undefined do
:ets.new(table_name, [
:set,
:named_table,
:public,
write_concurrency: true
])
end
end end
defp count_from_db do defp cached_values(name) do
Chain.fetch_count_consensus_block() table_name = table_name(name)
rescue
_e -> case :ets.lookup(table_name, @key) do
0 [{_, cached_values}] -> cached_values
_ -> nil
end
end end
defp current_time do defp current_time do
@ -81,4 +131,17 @@ defmodule Explorer.Chain.BlockCountCache do
DateTime.to_unix(utc_now, :millisecond) DateTime.to_unix(utc_now, :millisecond)
end end
defp period_from_env_var do
case System.get_env("BLOCK_COUNT_CACHE_PERIOD") do
value when is_binary(value) ->
case Integer.parse(value) do
{integer, ""} -> integer * 1_000
_ -> nil
end
_ ->
nil
end
end
end end

@ -17,13 +17,29 @@ defmodule Explorer.SmartContract.Verifier do
do: {:error, :contract_source_code} do: {:error, :contract_source_code}
def evaluate_authenticity(address_hash, params) do def evaluate_authenticity(address_hash, params) do
latest_evm_version = List.last(CodeCompiler.allowed_evm_versions())
evm_version = Map.get(params, "evm_version", latest_evm_version)
Enum.reduce([evm_version | previous_evm_versions(evm_version)], false, fn version, acc ->
case acc do
{:ok, _} = result ->
result
_ ->
cur_params = Map.put(params, "evm_version", version)
verify(address_hash, cur_params)
end
end)
end
defp verify(address_hash, params) do
name = Map.fetch!(params, "name") name = Map.fetch!(params, "name")
contract_source_code = Map.fetch!(params, "contract_source_code") contract_source_code = Map.fetch!(params, "contract_source_code")
optimization = Map.fetch!(params, "optimization") optimization = Map.fetch!(params, "optimization")
compiler_version = Map.fetch!(params, "compiler_version") compiler_version = Map.fetch!(params, "compiler_version")
external_libraries = Map.get(params, "external_libraries", %{}) external_libraries = Map.get(params, "external_libraries", %{})
constructor_arguments = Map.get(params, "constructor_arguments", "") constructor_arguments = Map.get(params, "constructor_arguments", "")
evm_version = Map.get(params, "evm_version", "byzantium") evm_version = Map.get(params, "evm_version")
optimization_runs = Map.get(params, "optimization_runs", 200) optimization_runs = Map.get(params, "optimization_runs", 200)
solc_output = solc_output =
@ -37,25 +53,7 @@ defmodule Explorer.SmartContract.Verifier do
external_libs: external_libraries external_libs: external_libraries
) )
case compare_bytecodes(solc_output, address_hash, constructor_arguments) do compare_bytecodes(solc_output, address_hash, constructor_arguments)
{:error, :generated_bytecode} ->
next_evm_version = next_evm_version(evm_version)
second_solc_output =
CodeCompiler.run(
name: name,
compiler_version: compiler_version,
code: contract_source_code,
optimize: optimization,
evm_version: next_evm_version,
external_libs: external_libraries
)
compare_bytecodes(second_solc_output, address_hash, constructor_arguments)
result ->
result
end
end end
defp compare_bytecodes({:error, :name}, _, _), do: {:error, :name} defp compare_bytecodes({:error, :name}, _, _), do: {:error, :name}
@ -114,16 +112,21 @@ defmodule Explorer.SmartContract.Verifier do
end end
end end
def next_evm_version(current_evm_version) do def previous_evm_versions(current_evm_version) do
[prev_version, last_version] = index = Enum.find_index(CodeCompiler.allowed_evm_versions(), fn el -> el == current_evm_version end)
CodeCompiler.allowed_evm_versions()
|> Enum.reverse() cond do
|> Enum.take(2) index == 0 ->
[]
if current_evm_version != last_version do index == 1 ->
last_version [List.first(CodeCompiler.allowed_evm_versions())]
else
prev_version true ->
[
Enum.at(CodeCompiler.allowed_evm_versions(), index - 1),
Enum.at(CodeCompiler.allowed_evm_versions(), index - 2)
]
end end
end end

@ -3,43 +3,54 @@ defmodule Explorer.Chain.BlockCountCacheTest do
alias Explorer.Chain.BlockCountCache alias Explorer.Chain.BlockCountCache
describe "count/0" do test "returns default transaction count" do
test "return count" do BlockCountCache.start_link(name: BlockTestCache)
insert(:block, number: 1, consensus: true)
insert(:block, number: 2, consensus: true)
insert(:block, number: 3, consensus: false)
BlockCountCache.setup() result = BlockCountCache.count(BlockTestCache)
assert BlockCountCache.count() == 2 assert is_nil(result)
end end
test "invalidates cache if period did pass" do test "updates cache if initial value is zero" do
insert(:block, number: 1, consensus: true) BlockCountCache.start_link(name: BlockTestCache)
Application.put_env(:explorer, BlockCountCache, ttl: 2_00) insert(:block, consensus: true)
BlockCountCache.setup() insert(:block, consensus: true)
insert(:block, consensus: false)
assert BlockCountCache.count() == 1 _result = BlockCountCache.count(BlockTestCache)
insert(:block, number: 2, consensus: true) Process.sleep(1000)
Process.sleep(2_000) updated_value = BlockCountCache.count(BlockTestCache)
assert BlockCountCache.count() == 2 assert updated_value == 2
end end
test "does not invalidate cache if period time did not pass" do test "does not update cache if cache period did not pass" do
insert(:block, number: 1, consensus: true) BlockCountCache.start_link(name: BlockTestCache)
Application.put_env(:explorer, BlockCountCache, ttl: 2_00) insert(:block, consensus: true)
BlockCountCache.setup() insert(:block, consensus: true)
insert(:block, consensus: false)
assert BlockCountCache.count() == 1 _result = BlockCountCache.count(BlockTestCache)
insert(:block, number: 2, consensus: true) Process.sleep(1000)
assert BlockCountCache.count() == 1 updated_value = BlockCountCache.count(BlockTestCache)
end
assert updated_value == 2
insert(:block, consensus: true)
insert(:block, consensus: true)
_updated_value = BlockCountCache.count(BlockTestCache)
Process.sleep(1000)
updated_value = BlockCountCache.count(BlockTestCache)
assert updated_value == 2
end end
end end

Loading…
Cancel
Save