Merge pull request #1933 from poanetwork/ab-eth-get-block-number

add eth_BlockNumber
pull/1975/head
Victor Baranov 6 years ago committed by GitHub
commit 8e9c179ccf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 8
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex
  3. 52
      apps/block_scout_web/lib/block_scout_web/etherscan.ex
  4. 28
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/block_view.ex
  5. 33
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/eth_rpc_view.ex
  6. 1
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs
  7. 2
      apps/explorer/config/config.exs
  8. 2
      apps/explorer/config/test.exs
  9. 57
      apps/explorer/lib/explorer/chain/block_number_cache.ex
  10. 64
      apps/explorer/test/explorer/chain/block_number_cache_test.exs
  11. 2
      apps/explorer/test/support/data_case.ex
  12. 14
      apps/indexer/lib/indexer/block/fetcher.ex

@ -22,6 +22,7 @@
- [#1920](https://github.com/poanetwork/blockscout/pull/1920) - fix: remove source code fields from list endpoint - [#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 - [#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 - [#1941](https://github.com/poanetwork/blockscout/pull/1941) - feat: add on demand fetching and stale attr to rpc
- [#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 - [#1952](https://github.com/poanetwork/blockscout/pull/1952) - feat: exclude empty contracts by default
### Fixes ### Fixes

@ -3,6 +3,7 @@ defmodule BlockScoutWeb.API.RPC.BlockController do
alias BlockScoutWeb.Chain, as: ChainWeb alias BlockScoutWeb.Chain, as: ChainWeb
alias Explorer.Chain alias Explorer.Chain
alias Explorer.Chain.BlockNumberCache
def getblockreward(conn, params) do def getblockreward(conn, params) do
with {:block_param, {:ok, unsafe_block_number}} <- {:block_param, Map.fetch(params, "blockno")}, with {:block_param, {:ok, unsafe_block_number}} <- {:block_param, Map.fetch(params, "blockno")},
@ -23,4 +24,11 @@ defmodule BlockScoutWeb.API.RPC.BlockController do
render(conn, :error, error: "Block does not exist") render(conn, :error, error: "Block does not exist")
end end
end end
def eth_block_number(conn, params) do
id = Map.get(params, "id", 1)
max_block_number = BlockNumberCache.max_number()
render(conn, :eth_block_number, number: max_block_number, id: id)
end
end end

@ -279,6 +279,12 @@ defmodule BlockScoutWeb.Etherscan do
"result" => nil "result" => nil
} }
@block_eth_block_number_example_value %{
"jsonrpc" => "2.0",
"result" => "767969",
"id" => 1
}
@contract_listcontracts_example_value %{ @contract_listcontracts_example_value %{
"status" => "1", "status" => "1",
"message" => "OK", "message" => "OK",
@ -476,11 +482,26 @@ defmodule BlockScoutWeb.Etherscan do
enum_interpretation: %{"0" => "error", "1" => "ok"} enum_interpretation: %{"0" => "error", "1" => "ok"}
} }
@jsonrpc_version_type %{
type: "string",
example: ~s("2.0")
}
@message_type %{ @message_type %{
type: "string", type: "string",
example: ~s("OK") example: ~s("OK")
} }
@hex_number_type %{
type: "string",
example: ~s("767969")
}
@id_type %{
type: "string",
example: ~s("1")
}
@wei_type %{ @wei_type %{
type: "wei", type: "wei",
definition: &__MODULE__.wei_type_definition/1, definition: &__MODULE__.wei_type_definition/1,
@ -1737,6 +1758,35 @@ defmodule BlockScoutWeb.Etherscan do
] ]
} }
@block_eth_block_number_action %{
name: "eth_block_number",
description: "Mimics Ethereum JSON RPC's eth_blockNumber. Returns the lastest block number",
required_params: [],
optional_params: [
%{
key: "id",
placeholder: "request id",
type: "integer",
description: "A nonnegative integer that represents the json rpc request id."
}
],
responses: [
%{
code: "200",
description: "successful request",
example_value: Jason.encode!(@block_eth_block_number_example_value),
model: %{
name: "Result",
fields: %{
jsonrpc: @jsonrpc_version_type,
id: @id_type,
result: @hex_number_type
}
}
}
]
}
@block_getblockreward_action %{ @block_getblockreward_action %{
name: "getblockreward", name: "getblockreward",
description: "Get block reward by block number.", description: "Get block reward by block number.",
@ -2171,7 +2221,7 @@ defmodule BlockScoutWeb.Etherscan do
@block_module %{ @block_module %{
name: "block", name: "block",
actions: [@block_getblockreward_action] actions: [@block_getblockreward_action, @block_eth_block_number_action]
} }
@contract_module %{ @contract_module %{

@ -1,7 +1,7 @@
defmodule BlockScoutWeb.API.RPC.BlockView do defmodule BlockScoutWeb.API.RPC.BlockView do
use BlockScoutWeb, :view use BlockScoutWeb, :view
alias BlockScoutWeb.API.RPC.RPCView alias BlockScoutWeb.API.RPC.{EthRPCView, RPCView}
alias Explorer.Chain.{Hash, Wei} alias Explorer.Chain.{Hash, Wei}
def render("block_reward.json", %{block: block, reward: reward}) do def render("block_reward.json", %{block: block, reward: reward}) do
@ -22,7 +22,33 @@ defmodule BlockScoutWeb.API.RPC.BlockView do
RPCView.render("show.json", data: data) RPCView.render("show.json", data: data)
end end
def render("eth_block_number.json", %{number: number, id: id}) do
result = encode_quantity(number)
EthRPCView.render("show.json", %{result: result, id: id})
end
def render("error.json", %{error: error}) do def render("error.json", %{error: error}) do
RPCView.render("error.json", error: error) RPCView.render("error.json", error: error)
end end
defp encode_quantity(binary) when is_binary(binary) do
hex_binary = Base.encode16(binary, case: :lower)
result = String.replace_leading(hex_binary, "0", "")
final_result = if result == "", do: "0", else: result
"0x#{final_result}"
end
defp encode_quantity(value) when is_integer(value) do
value
|> :binary.encode_unsigned()
|> encode_quantity()
end
defp encode_quantity(value) when is_nil(value) do
nil
end
end end

@ -0,0 +1,33 @@
defmodule BlockScoutWeb.API.RPC.EthRPCView do
use BlockScoutWeb, :view
defstruct [:result, :id, :error]
def render("show.json", %{result: result, id: id}) do
%__MODULE__{
result: result,
id: id
}
end
def render("error.json", %{error: message, id: id}) do
%__MODULE__{
error: message,
id: id
}
end
defimpl Poison.Encoder, for: BlockScoutWeb.API.RPC.EthRPCView do
def encode(%BlockScoutWeb.API.RPC.EthRPCView{result: result, id: id, error: error}, _options) when is_nil(error) do
"""
{"jsonrpc":"2.0","result":"#{result}","id":#{id}}
"""
end
def encode(%BlockScoutWeb.API.RPC.EthRPCView{id: id, error: error}, _options) do
"""
{"jsonrpc":"2.0","error": #{error},"id": #{id}}
"""
end
end
end

@ -25,7 +25,6 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
start_supervised!(AddressesWithBalanceCounter) start_supervised!(AddressesWithBalanceCounter)
Application.put_env(:explorer, AverageBlockTime, enabled: true) Application.put_env(:explorer, AverageBlockTime, enabled: true)
BlockNumberCache.setup(cache_period: 0)
on_exit(fn -> on_exit(fn ->
Application.put_env(:explorer, AverageBlockTime, enabled: false) Application.put_env(:explorer, AverageBlockTime, enabled: false)

@ -13,6 +13,8 @@ config :explorer,
config :explorer, Explorer.Counters.AverageBlockTime, enabled: true config :explorer, Explorer.Counters.AverageBlockTime, enabled: true
config :explorer, Explorer.Chain.BlockNumberCache, enabled: true
config :explorer, Explorer.ExchangeRates.Source.CoinMarketCap, config :explorer, Explorer.ExchangeRates.Source.CoinMarketCap,
pages: String.to_integer(System.get_env("COINMARKETCAP_PAGES") || "10") pages: String.to_integer(System.get_env("COINMARKETCAP_PAGES") || "10")

@ -13,6 +13,8 @@ config :explorer, Explorer.Repo,
config :explorer, Explorer.ExchangeRates, enabled: false, store: :ets config :explorer, Explorer.ExchangeRates, enabled: false, store: :ets
config :explorer, Explorer.Chain.BlockNumberCache, enabled: false
config :explorer, Explorer.KnownTokens, enabled: false, store: :ets config :explorer, Explorer.KnownTokens, enabled: false, store: :ets
config :explorer, Explorer.Counters.AverageBlockTime, enabled: false config :explorer, Explorer.Counters.AverageBlockTime, enabled: false

@ -6,13 +6,10 @@ defmodule Explorer.Chain.BlockNumberCache do
alias Explorer.Chain alias Explorer.Chain
@tab :block_number_cache @tab :block_number_cache
# 30 minutes
@cache_period 1_000 * 60 * 30
@key "min_max" @key "min_max"
@opts_key "opts"
@spec setup(Keyword.t()) :: :ok @spec setup() :: :ok
def setup(opts \\ []) do def setup do
if :ets.whereis(@tab) == :undefined do if :ets.whereis(@tab) == :undefined do
:ets.new(@tab, [ :ets.new(@tab, [
:set, :set,
@ -22,7 +19,6 @@ defmodule Explorer.Chain.BlockNumberCache do
]) ])
end end
setup_opts(opts)
update_cache() update_cache()
:ok :ok
@ -41,15 +37,11 @@ defmodule Explorer.Chain.BlockNumberCache do
end end
defp value(type) do defp value(type) do
initial_cache = {_min, _max, old_current_time} = cached_values() {min, max} =
if Application.get_env(:explorer, __MODULE__)[:enabled] do
{min, max, _current_time} =
if current_time() - old_current_time > cache_period() do
update_cache()
cached_values() cached_values()
else else
initial_cache min_and_max_from_db()
end end
case type do case type do
@ -59,18 +51,29 @@ defmodule Explorer.Chain.BlockNumberCache do
end end
end end
defp update_cache do @spec update(non_neg_integer()) :: boolean()
current_time = current_time() def update(number) do
{min, max} = min_and_max_from_db() {old_min, old_max} = cached_values()
tuple = {min, max, current_time}
:ets.insert(@tab, {@key, tuple}) cond do
number > old_max ->
tuple = {old_min, number}
:ets.insert(@tab, {@key, tuple})
number < old_min ->
tuple = {number, old_max}
:ets.insert(@tab, {@key, tuple})
true ->
false
end
end end
defp setup_opts(opts) do defp update_cache do
cache_period = opts[:cache_period] || @cache_period {min, max} = min_and_max_from_db()
tuple = {min, max}
:ets.insert(@tab, {@opts_key, cache_period}) :ets.insert(@tab, {@key, tuple})
end end
defp cached_values do defp cached_values do
@ -79,22 +82,10 @@ defmodule Explorer.Chain.BlockNumberCache do
cached_values cached_values
end end
defp cache_period do
[{_, cache_period}] = :ets.lookup(@tab, @opts_key)
cache_period
end
defp min_and_max_from_db do defp min_and_max_from_db do
Chain.fetch_min_and_max_block_numbers() Chain.fetch_min_and_max_block_numbers()
rescue rescue
_e -> _e ->
{0, 0} {0, 0}
end end
defp current_time do
utc_now = DateTime.utc_now()
DateTime.to_unix(utc_now, :millisecond)
end
end end

@ -3,6 +3,14 @@ defmodule Explorer.Chain.BlockNumberCacheTest do
alias Explorer.Chain.BlockNumberCache alias Explorer.Chain.BlockNumberCache
setup do
Application.put_env(:explorer, Explorer.Chain.BlockNumberCache, enabled: true)
on_exit(fn ->
Application.put_env(:explorer, Explorer.Chain.BlockNumberCache, enabled: false)
end)
end
describe "max_number/1" do describe "max_number/1" do
test "returns max number" do test "returns max number" do
insert(:block, number: 5) insert(:block, number: 5)
@ -11,33 +19,6 @@ defmodule Explorer.Chain.BlockNumberCacheTest do
assert BlockNumberCache.max_number() == 5 assert BlockNumberCache.max_number() == 5
end end
test "invalidates cache if period did pass" do
insert(:block, number: 5)
BlockNumberCache.setup(cache_period: 2_000)
assert BlockNumberCache.max_number() == 5
insert(:block, number: 10)
Process.sleep(2_000)
assert BlockNumberCache.max_number() == 10
assert BlockNumberCache.min_number() == 5
end
test "does not invalidate cache if period time did not pass" do
insert(:block, number: 5)
BlockNumberCache.setup(cache_period: 10_000)
assert BlockNumberCache.max_number() == 5
insert(:block, number: 10)
assert BlockNumberCache.max_number() == 5
end
end end
describe "min_number/1" do describe "min_number/1" do
@ -48,32 +29,31 @@ defmodule Explorer.Chain.BlockNumberCacheTest do
assert BlockNumberCache.max_number() == 2 assert BlockNumberCache.max_number() == 2
end end
end
test "invalidates cache" do describe "update/1" do
insert(:block, number: 5) test "updates max number" do
insert(:block, number: 2)
BlockNumberCache.setup(cache_period: 2_000)
assert BlockNumberCache.min_number() == 5 BlockNumberCache.setup()
insert(:block, number: 2) assert BlockNumberCache.max_number() == 2
Process.sleep(2_000) assert BlockNumberCache.update(3)
assert BlockNumberCache.min_number() == 2 assert BlockNumberCache.max_number() == 3
assert BlockNumberCache.max_number() == 5
end end
test "does not invalidate cache if period time did not pass" do test "updates min number" do
insert(:block, number: 5) insert(:block, number: 2)
BlockNumberCache.setup(cache_period: 10_000) BlockNumberCache.setup()
assert BlockNumberCache.max_number() == 5 assert BlockNumberCache.min_number() == 2
insert(:block, number: 2) assert BlockNumberCache.update(1)
assert BlockNumberCache.max_number() == 5 assert BlockNumberCache.min_number() == 1
end end
end end
end end

@ -39,7 +39,7 @@ defmodule Explorer.DataCase do
Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, {:shared, self()}) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, {:shared, self()})
end end
Explorer.Chain.BlockNumberCache.setup(cache_period: 0) Explorer.Chain.BlockNumberCache.setup()
:ok :ok
end end

@ -11,7 +11,7 @@ defmodule Indexer.Block.Fetcher do
alias EthereumJSONRPC.{Blocks, FetchedBeneficiaries} alias EthereumJSONRPC.{Blocks, FetchedBeneficiaries}
alias Explorer.Chain alias Explorer.Chain
alias Explorer.Chain.{Address, Block, Hash, Import, Transaction} alias Explorer.Chain.{Address, Block, BlockNumberCache, Hash, Import, Transaction}
alias Indexer.Block.Fetcher.Receipts alias Indexer.Block.Fetcher.Receipts
alias Indexer.Fetcher.{ alias Indexer.Fetcher.{
@ -171,13 +171,23 @@ defmodule Indexer.Block.Fetcher do
transactions: %{params: transactions_with_receipts} transactions: %{params: transactions_with_receipts}
} }
) do ) do
{:ok, %{inserted: inserted, errors: blocks_errors}} result = {:ok, %{inserted: inserted, errors: blocks_errors}}
update_block_cache(inserted[:blocks])
result
else else
{step, {:error, reason}} -> {:error, {step, reason}} {step, {:error, reason}} -> {:error, {step, reason}}
{:import, {:error, step, failed_value, changes_so_far}} -> {:error, {step, failed_value, changes_so_far}} {:import, {:error, step, failed_value, changes_so_far}} -> {:error, {step, failed_value, changes_so_far}}
end end
end end
defp update_block_cache(blocks) do
max_block = Enum.max_by(blocks, fn block -> block.number end)
min_block = Enum.min_by(blocks, fn block -> block.number end)
BlockNumberCache.update(max_block.number)
BlockNumberCache.update(min_block.number)
end
def import( def import(
%__MODULE__{broadcast: broadcast, callback_module: callback_module} = state, %__MODULE__{broadcast: broadcast, callback_module: callback_module} = state,
options options

Loading…
Cancel
Save