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
- [#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
- [#1876](https://github.com/poanetwork/blockscout/pull/1876) - async calculate a count of blocks
### Fixes
@ -29,6 +30,7 @@
- [#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
- [#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

@ -3,7 +3,7 @@ defmodule BlockScoutWeb.ChainController do
alias BlockScoutWeb.ChainView
alias Explorer.{Chain, PagingOptions, Repo}
alias Explorer.Chain.{Address, Block, BlockCountCache, Transaction}
alias Explorer.Chain.{Address, Block, Transaction}
alias Explorer.Counters.AverageBlockTime
alias Explorer.ExchangeRates.Token
alias Explorer.Market
@ -11,7 +11,7 @@ defmodule BlockScoutWeb.ChainController do
def show(conn, _params) do
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()

@ -82,9 +82,6 @@ config :spandex_ecto, SpandexEcto.EctoLogger,
tracer: Explorer.Tracer,
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
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs"

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

@ -29,6 +29,7 @@ defmodule Explorer.Chain do
Address.CurrentTokenBalance,
Address.TokenBalance,
Block,
BlockCountCache,
BlockNumberCache,
Data,
DecompiledSmartContract,
@ -1953,6 +1954,22 @@ defmodule Explorer.Chain do
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 """
`t:Explorer.Chain.InternalTransaction/0`s in `t:Explorer.Chain.Transaction.t/0` with `hash`.

@ -1,79 +1,129 @@
defmodule Explorer.Chain.BlockCountCache do
@moduledoc """
Cache for count consensus blocks.
Cache for block count.
"""
require Logger
use GenServer
alias Explorer.Chain
@tab :block_count_cache
# 10 minutes
@cache_period 1_000 * 60 * 10
@key "count"
@opts_key "opts"
@default_value nil
@name __MODULE__
@spec setup() :: :ok
def setup do
if :ets.whereis(@tab) == :undefined do
:ets.new(@tab, [
:set,
:named_table,
:public,
write_concurrency: true
])
def start_link(params) do
name = params[:name] || @name
params_with_name = Keyword.put(params, :name, name)
GenServer.start_link(__MODULE__, params_with_name, name: name)
end
setup_opts()
update_cache()
def init(params) do
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
def count do
initial_cache = {_count, old_current_time} = cached_values()
def count(process_name \\ __MODULE__) do
GenServer.call(process_name, :count)
end
{count, _current_time} =
if current_time() - old_current_time > cache_period() do
update_cache()
def handle_call(:count, _, {{cache_period, default_value, name}, task}) do
{count, task} =
case cached_values(name) do
nil ->
{default_value, update_cache(task, name)}
cached_values()
else
initial_cache
{cached_value, timestamp} ->
task =
if current_time() - timestamp > cache_period do
update_cache(task, name)
end
count
{cached_value, task}
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()
count = count_from_db()
tuple = {count, current_time}
tuple = {value, 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
defp setup_opts do
cache_period = Application.get_env(:explorer, __MODULE__)[:ttl] || @cache_period
def handle_info(_, {{cache_period, default_value, name}, _}) do
{: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
defp cached_values do
[{_, cached_values}] = :ets.lookup(@tab, @key)
def async_update_cache(name) do
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
defp cache_period do
[{_, cache_period}] = :ets.lookup(@tab, @opts_key)
defp init_ets_table(name) do
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
defp count_from_db do
Chain.fetch_count_consensus_block()
rescue
_e ->
0
defp cached_values(name) do
table_name = table_name(name)
case :ets.lookup(table_name, @key) do
[{_, cached_values}] -> cached_values
_ -> nil
end
end
defp current_time do
@ -81,4 +131,17 @@ defmodule Explorer.Chain.BlockCountCache do
DateTime.to_unix(utc_now, :millisecond)
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

@ -17,13 +17,29 @@ defmodule Explorer.SmartContract.Verifier do
do: {:error, :contract_source_code}
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")
contract_source_code = Map.fetch!(params, "contract_source_code")
optimization = Map.fetch!(params, "optimization")
compiler_version = Map.fetch!(params, "compiler_version")
external_libraries = Map.get(params, "external_libraries", %{})
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)
solc_output =
@ -37,25 +53,7 @@ defmodule Explorer.SmartContract.Verifier do
external_libs: external_libraries
)
case compare_bytecodes(solc_output, address_hash, constructor_arguments) do
{: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
compare_bytecodes(solc_output, address_hash, constructor_arguments)
end
defp compare_bytecodes({:error, :name}, _, _), do: {:error, :name}
@ -114,16 +112,21 @@ defmodule Explorer.SmartContract.Verifier do
end
end
def next_evm_version(current_evm_version) do
[prev_version, last_version] =
CodeCompiler.allowed_evm_versions()
|> Enum.reverse()
|> Enum.take(2)
def previous_evm_versions(current_evm_version) do
index = Enum.find_index(CodeCompiler.allowed_evm_versions(), fn el -> el == current_evm_version end)
cond do
index == 0 ->
[]
if current_evm_version != last_version do
last_version
else
prev_version
index == 1 ->
[List.first(CodeCompiler.allowed_evm_versions())]
true ->
[
Enum.at(CodeCompiler.allowed_evm_versions(), index - 1),
Enum.at(CodeCompiler.allowed_evm_versions(), index - 2)
]
end
end

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

Loading…
Cancel
Save