Merge branch 'master' into ab-do-not-fetch-simple-token-transfers-internal-transactions

pull/1341/head
Ayrat Badykov 6 years ago committed by GitHub
commit 0c1e18452e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      apps/explorer/lib/explorer/chain/address.ex
  2. 179
      apps/explorer/lib/explorer/chain/smart_contract.ex
  3. 6
      apps/explorer/lib/explorer/counters/average_block_time.ex
  4. 46
      apps/indexer/lib/indexer/block/realtime/fetcher.ex

@ -22,7 +22,9 @@ defmodule Explorer.Chain.Address do
* `fetched_coin_balance_block_number` - the `t:Explorer.Chain.Block.t/0` `t:Explorer.Chain.Block.block_number/0` for
which `fetched_coin_balance` was fetched
* `hash` - the hash of the address's public key
* `contract_code` - the code of the contract when an Address is a contract
* `contract_code` - the binary code of the contract when an Address is a contract. The human-readable
Solidity source code is in `smart_contract` `t:Explorer.Chain.SmartContract.t/0` `contract_source_code` *if* the
contract has been verified
* `names` - names known for the address
* `inserted_at` - when this address was inserted
* `updated_at` when this address was last updated

@ -12,12 +12,189 @@ defmodule Explorer.Chain.SmartContract do
use Explorer.Schema
@typedoc """
The name of a parameter to a function or event.
"""
@type parameter_name :: String.t()
@typedoc """
Canonical Input or output [type](https://solidity.readthedocs.io/en/develop/abi-spec.html#types).
* `"address"` - equivalent to `uint160`, except for the assumed interpretation and language typing. For computing the
function selector, `address` is used.
* `"bool"` - equivalent to uint8 restricted to the values 0 and 1. For computing the function selector, bool is used.
* `bytes`: dynamic sized byte sequence
* `"bytes<M>"` - binary type of `M` bytes, `0 < M <= 32`.
* `"fixed"` - synonym for `"fixed128x18". For computing the function selection, `"fixed128x8"` has to be used.
* `"fixed<M>x<N>"` - signed fixed-point decimal number of `M` bits, `8 <= M <= 256`, `M % 8 ==0`, and `0 < N <= 80`,
which denotes the value `v` as `v / (10 ** N)`.
* `"function" - an address (`20` bytes) followed by a function selector (`4` bytes). Encoded identical to `bytes24`.
* `"int"` - synonym for `"int256"`. For computing the function selector `"int256"` has to be used.
* `"int<M>"` - twos complement signed integer type of `M` bits, `0 < M <= 256`, `M % 8 == 0`.
* `"string"` - dynamic sized unicode string assumed to be UTF-8 encoded.
* `"tuple"` - a tuple.
* `"(<T1>,<T2>,...,<Tn>)"` - tuple consisting of the `t:type/0`s `<T1>`, , `Tn`, `n >= 0`.
* `"<type>[]"` - a variable-length array of elements of the given `type`.
* `"<type>[M]"` - a fixed-length array of `M` elements, `M >= 0`, of the given `t:type/0`.
* `"ufixed"` - synonym for `"ufixed128x18". For computing the function selection, `"ufixed128x8"` has to be used.
* `"ufixed<M>x<N>"` - unsigned variant of `"fixed<M>x<N>"`
* `"uint"` - synonym for `"uint256"`. For computing the function selector `"uint256"` has to be used.
* `"uint<M>"` - unsigned integer type of `M` bits, `0 < M <= 256`, `M % 8 == 0.` e.g. `uint32`, `uint8`, `uint256`.
"""
@type type :: String.t()
@typedoc """
Name of component.
"""
@type component_name :: String.t()
@typedoc """
A component of a [tuple](https://solidity.readthedocs.io/en/develop/abi-spec.html#handling-tuple-types).
* `"name"` - name of the component.
* `"type"` - `t:type/0`.
"""
@type component :: %{String.t() => component_name() | type()}
@typedoc """
The components of a [tuple](https://solidity.readthedocs.io/en/develop/abi-spec.html#handling-tuple-types).
"""
@type components :: [component()]
@typedoc """
* `"event"`
"""
@type event_type :: String.t()
@typedoc """
Name of an event in an `t:abi/0`.
"""
@type event_name :: String.t()
@typedoc """
* `true` - if field is part of the `t:Explorer.Chain.Log.t/0` `topics`.
* `false` - if field is part of the `t:Explorer.Chain.Log.t/0` `data`.
"""
@type indexed :: boolean()
@typedoc """
* `"name"` - `t:parameter_name/0`.
* `"type"` - `t:type/0`.
* `"components" `- `t:components/0` used when `"type"` is a tuple type.
* `"indexed"` - `t:indexed/0`.
"""
@type event_input :: %{String.t() => parameter_name() | type() | components() | indexed()}
@typedoc """
* `true` - event was declared as `anonymous`.
* `false` - otherwise.
"""
@type anonymous :: boolean()
@typedoc """
* `"type" - `t:event_type/0`
* `"name"` - `t:event_name/0`
* `"inputs"` - `t:list/0` of `t:event_input/0`.
* `"anonymous"` - t:anonymous/0`
"""
@type event_description :: %{String.t() => term()}
@typedoc """
* `"function"`
* `"constructor"`
* `"fallback"` - the default, unnamed function
"""
@type function_type :: String.t()
@typedoc """
Name of a function in an `t:abi/0`.
"""
@type function_name :: String.t()
@typedoc """
* `"name"` - t:parameter_name/0`.
* `"type"` - `t:type/0`.
* `"components"` - `t:components/0` used when `"type"` is a tuple type.
"""
@type function_input :: %{String.t() => parameter_name() | type() | components()}
@typedoc """
* `"type" - `t:type/0`
"""
@type function_output :: %{String.t() => type()}
@typedoc """
* `"pure"` - [specified to not read blockchain state](https://solidity.readthedocs.io/en/develop/contracts.html#pure-functions).
* `"view"` - [specified to not modify the blockchain state](https://solidity.readthedocs.io/en/develop/contracts.html#view-functions).
* `"nonpayable"` - function does not accept Ether.
**NOTE**: Sending non-zero Ether to non-payable function will revert the transaction.
* `"payable"` - function accepts Ether.
"""
@type state_mutability :: String.t()
@typedoc """
**Deprecated:** Use `t:function_description/0` `"stateMutability"`:
* `true` - `"payable"`
* `false` - `"pure"`, `"view"`, or `"nonpayable"`.
"""
@type payable :: boolean()
@typedoc """
**Deprecated:** Use `t:function_description/0` `"stateMutability"`:
* `true` - `"pure"` or `"view"`.
* `false` - `"nonpayable"` or `"payable"`.
"""
@type constant :: boolean()
@typedoc """
The [function description](https://solidity.readthedocs.io/en/develop/abi-spec.html#json) for a function in the
`t:abi.t/0`.
* `"type"` - `t:function_type/0`
* `"name" - `t:function_name/0`
* `"inputs` - `t:list/0` of `t:function_input/0`.
* `"outputs" - `t:list/0` of `t:output/0`.
* `"stateMutability"` - `t:state_mutability/0`
* `"payable"` - `t:payable/0`.
**WARNING:** Deprecated and will be removed in the future. Use `"stateMutability"` instead.
* `"constant"` - `t:constant/0`.
**WARNING:** Deprecated and will be removed in the future. Use `"stateMutability"` instead.
"""
@type function_description :: %{
String.t() =>
function_type()
| function_name()
| [function_input()]
| [function_output()]
| state_mutability()
| payable()
| constant()
}
@typedoc """
The [JSON ABI specification](https://solidity.readthedocs.io/en/develop/abi-spec.html#json) for a contract.
"""
@type abi :: [event_description | function_description]
@typedoc """
* `name` - the human-readable name of the smart contract.
* `compiler_version` - the version of the Solidity compiler used to compile `contract_source_code` with `optimization`
into `address` `t:Explorer.Chain.Address.t/0` `contract_code`.
* `optimization` - whether optimizations were turned on when compiling `contract_source_code` into `address`
`t:Explorer.Chain.Address.t/0` `contract_code`.
* `contract_source_code` - the Solidity source code that was compiled by `compiler_version` with `optimization` to
produce `address` `t:Explorer.Chain.Address.t/0` `contract_code`.
* `abi` - The [JSON ABI specification](https://solidity.readthedocs.io/en/develop/abi-spec.html#json) for this
contract.
"""
@type t :: %Explorer.Chain.SmartContract{
name: String.t(),
compiler_version: String.t(),
optimization: boolean,
contract_source_code: String.t(),
abi: {:array, :map}
abi: [function_description]
}
schema "smart_contracts" do

@ -64,7 +64,10 @@ defmodule Explorer.Counters.AverageBlockTime do
# This is pretty naive, but we'll only ever be sorting 100 dates so I don't think
# complex logic is really necessary here.
defp add_block(%{timestamps: timestamps} = state, block) do
defp add_block(%{timestamps: timestamps} = state, {new_number, _} = block) do
if Enum.any?(timestamps, fn {number, _} -> number == new_number end) do
state
else
timestamps =
[block | timestamps]
|> Enum.sort_by(fn {number, _} -> number end, &Kernel.>/2)
@ -72,6 +75,7 @@ defmodule Explorer.Counters.AverageBlockTime do
%{state | timestamps: timestamps, average: average_distance(timestamps)}
end
end
defp average_distance([]), do: Duration.from_milliseconds(0)
defp average_distance([_]), do: Duration.from_milliseconds(0)

@ -17,15 +17,15 @@ defmodule Indexer.Block.Realtime.Fetcher do
alias EthereumJSONRPC.{FetchedBalances, Subscription}
alias Explorer.Chain
alias Explorer.Chain.TokenTransfer
alias Explorer.Counters.AverageBlockTime
alias Indexer.{AddressExtraction, Block, TokenBalances, Tracer}
alias Indexer.Block.Realtime.{ConsensusEnsurer, TaskSupervisor}
@polling_period 2_000
alias Timex.Duration
@behaviour Block.Fetcher
@enforce_keys ~w(block_fetcher)a
defstruct ~w(block_fetcher subscription previous_number max_number_seen)a
defstruct ~w(block_fetcher subscription previous_number max_number_seen timer)a
@type t :: %__MODULE__{
block_fetcher: %Block.Fetcher{
@ -57,8 +57,13 @@ defmodule Indexer.Block.Realtime.Fetcher do
def handle_continue({:init, subscribe_named_arguments}, %__MODULE__{subscription: nil} = state)
when is_list(subscribe_named_arguments) do
case EthereumJSONRPC.subscribe("newHeads", subscribe_named_arguments) do
{:ok, subscription} -> {:noreply, %__MODULE__{state | subscription: subscription}}
{:error, reason} -> {:stop, reason, state}
{:ok, subscription} ->
timer = schedule_polling()
{:noreply, %__MODULE__{state | subscription: subscription, timer: timer}}
{:error, reason} ->
{:stop, reason, state}
end
end
@ -69,25 +74,27 @@ defmodule Indexer.Block.Realtime.Fetcher do
block_fetcher: %Block.Fetcher{} = block_fetcher,
subscription: %Subscription{} = subscription,
previous_number: previous_number,
max_number_seen: max_number_seen
max_number_seen: max_number_seen,
timer: timer
} = state
)
when is_binary(quantity) do
number = quantity_to_integer(quantity)
# Subscriptions don't support getting all the blocks and transactions data,
# so we need to go back and get the full block
start_fetch_and_import(number, block_fetcher, previous_number, max_number_seen)
new_max_number = new_max_number(number, max_number_seen)
schedule_polling()
:timer.cancel(timer)
new_timer = schedule_polling()
{:noreply,
%{
state
| previous_number: number,
max_number_seen: new_max_number
max_number_seen: new_max_number,
timer: new_timer
}}
end
@ -102,22 +109,23 @@ defmodule Indexer.Block.Realtime.Fetcher do
) do
{number, new_max_number} =
case EthereumJSONRPC.fetch_block_number_by_tag("latest", json_rpc_named_arguments) do
{:ok, number} ->
start_fetch_and_import(number, block_fetcher, previous_number, max_number_seen)
{:ok, number} when is_nil(max_number_seen) or number > max_number_seen ->
start_fetch_and_import(number, block_fetcher, previous_number, number)
{number, new_max_number(number, max_number_seen)}
{max_number_seen, number}
{:error, _} ->
_ ->
{previous_number, max_number_seen}
end
schedule_polling()
timer = schedule_polling()
{:noreply,
%{
state
| previous_number: number,
max_number_seen: new_max_number
max_number_seen: new_max_number,
timer: timer
}}
end
@ -126,7 +134,13 @@ defmodule Indexer.Block.Realtime.Fetcher do
defp new_max_number(number, max_number_seen), do: max(number, max_number_seen)
defp schedule_polling do
Process.send_after(self(), :poll_latest_block_number, @polling_period)
polling_period =
case AverageBlockTime.average_block_time() do
{:error, :disabled} -> 2_000
block_time -> round(Duration.to_milliseconds(block_time) * 2)
end
Process.send_after(self(), :poll_latest_block_number, polling_period)
end
@import_options ~w(address_hash_to_fetched_balance_block_number)a

Loading…
Cancel
Save