Merge branch 'master' into ab-async-contract-verification

pull/2264/head
Ayrat Badykov 5 years ago committed by GitHub
commit d45cebff5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      CHANGELOG.md
  2. 1
      README.md
  3. 2
      apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex
  4. 2
      apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex
  5. 4
      apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex
  6. 2
      apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex
  7. 4
      apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex
  8. 2
      apps/block_scout_web/lib/block_scout_web/controllers/address_validation_controller.ex
  9. 4
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/holder_controller.ex
  10. 4
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex
  11. 4
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/read_contract_controller.ex
  12. 4
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/transfer_controller.ex
  13. 5
      apps/block_scout_web/lib/block_scout_web/views/api_docs_view.ex
  14. 25
      apps/block_scout_web/test/block_scout_web/views/api_docs_view_test.exs
  15. 95
      apps/explorer/lib/explorer/chain.ex
  16. 4
      apps/explorer/test/explorer/chain_test.exs
  17. 4
      apps/indexer/lib/indexer/fetcher/token.ex
  18. 4
      apps/indexer/lib/indexer/fetcher/token_updater.ex

@ -5,14 +5,17 @@
- [#2294](https://github.com/poanetwork/blockscout/pull/2294) - add healthy block period checking endpoint - [#2294](https://github.com/poanetwork/blockscout/pull/2294) - add healthy block period checking endpoint
### Fixes ### Fixes
- [#2310](https://github.com/poanetwork/blockscout/pull/2310) - parse url for api docs
- [#2299](https://github.com/poanetwork/blockscout/pull/2299) - fix interpolation in error message - [#2299](https://github.com/poanetwork/blockscout/pull/2299) - fix interpolation in error message
- [#2303](https://github.com/poanetwork/blockscout/pull/2303) - fix transaction csv download link - [#2303](https://github.com/poanetwork/blockscout/pull/2303) - fix transaction csv download link
- [#2304](https://github.com/poanetwork/blockscout/pull/2304) - footer grid fix for md resolution - [#2304](https://github.com/poanetwork/blockscout/pull/2304) - footer grid fix for md resolution
- [#2291](https://github.com/poanetwork/blockscout/pull/2291) - dashboard fix for md resolution, transactions load fix, block info row fix, addresses page issue, check mark issue - [#2291](https://github.com/poanetwork/blockscout/pull/2291) - dashboard fix for md resolution, transactions load fix, block info row fix, addresses page issue, check mark issue
### Chore ### Chore
- [#2305](https://github.com/poanetwork/blockscout/pull/2305) - Improve Address controllers
- [#2302](https://github.com/poanetwork/blockscout/pull/2302) - fix names for xDai source - [#2302](https://github.com/poanetwork/blockscout/pull/2302) - fix names for xDai source
- [#2289](https://github.com/poanetwork/blockscout/pull/2289) - Optional websockets for dev environment - [#2289](https://github.com/poanetwork/blockscout/pull/2289) - Optional websockets for dev environment
- [#2307](https://github.com/poanetwork/blockscout/pull/2307) - add GoJoy to README
## 2.0.1-beta ## 2.0.1-beta

@ -41,6 +41,7 @@ Currently available full-featured block explorers (Etherscan, Etherchain, Blockc
| | | [Kotti Testnet](https://kottiexplorer.ethernode.io/) | | | | [Kotti Testnet](https://kottiexplorer.ethernode.io/) |
| | | [Loom](http://plasma-blockexplorer.dappchains.com/) | | | | [Loom](http://plasma-blockexplorer.dappchains.com/) |
| | | [Tenda](https://tenda.network) | | | | [Tenda](https://tenda.network) |
| | | [GoJoy Chain](https://gojoychain.com/) |
Current BlockScout versions for hosted projects are available [on the forum](https://forum.poa.network/t/deployed-instances-on-blockscout-com/1938). Current BlockScout versions for hosted projects are available [on the forum](https://forum.poa.network/t/deployed-instances-on-blockscout-com/1938).

@ -16,7 +16,7 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do
def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do {:ok, address} <- Chain.hash_to_address(address_hash, [], false) do
full_options = paging_options(params) full_options = paging_options(params)
coin_balances_plus_one = Chain.address_to_coin_balances(address_hash, full_options) coin_balances_plus_one = Chain.address_to_coin_balances(address_hash, full_options)

@ -16,7 +16,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do
def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do {:ok, address} <- Chain.hash_to_address(address_hash, [], false) do
full_options = full_options =
[ [
necessity_by_association: %{ necessity_by_association: %{

@ -16,7 +16,7 @@ defmodule BlockScoutWeb.AddressLogsController do
def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do {:ok, address} <- Chain.hash_to_address(address_hash, [], false) do
logs_plus_one = Chain.address_to_logs(address, paging_options(params)) logs_plus_one = Chain.address_to_logs(address, paging_options(params))
{results, next_page} = split_list_by_page(logs_plus_one) {results, next_page} = split_list_by_page(logs_plus_one)
@ -74,7 +74,7 @@ defmodule BlockScoutWeb.AddressLogsController do
def search_logs(conn, %{"topic" => topic, "address_id" => address_hash_string} = params) do def search_logs(conn, %{"topic" => topic, "address_id" => address_hash_string} = params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do {:ok, address} <- Chain.hash_to_address(address_hash, [], false) do
topic = String.trim(topic) topic = String.trim(topic)
formatted_topic = if String.starts_with?(topic, "0x"), do: topic, else: "0x" <> topic formatted_topic = if String.starts_with?(topic, "0x"), do: topic, else: "0x" <> topic

@ -12,7 +12,7 @@ defmodule BlockScoutWeb.AddressTokenController do
def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do {:ok, address} <- Chain.hash_to_address(address_hash, [], false) do
tokens_plus_one = Chain.address_tokens_with_balance(address_hash, paging_options(params)) tokens_plus_one = Chain.address_tokens_with_balance(address_hash, paging_options(params))
{tokens, next_page} = split_list_by_page(tokens_plus_one) {tokens, next_page} = split_list_by_page(tokens_plus_one)

@ -28,8 +28,10 @@ defmodule BlockScoutWeb.AddressTransactionController do
] ]
def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do
address_options = [necessity_by_association: %{:names => :optional}]
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash, [:names], false) do {:ok, address} <- Chain.hash_to_address(address_hash, address_options, false) do
options = options =
@transaction_necessity_by_association @transaction_necessity_by_association
|> put_in([:necessity_by_association, :block], :required) |> put_in([:necessity_by_association, :block], :required)

@ -17,7 +17,7 @@ defmodule BlockScoutWeb.AddressValidationController do
def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.find_or_insert_address_from_hash(address_hash) do {:ok, address} <- Chain.find_or_insert_address_from_hash(address_hash, [], false) do
full_options = full_options =
Keyword.merge( Keyword.merge(
[ [

@ -43,8 +43,10 @@ defmodule BlockScoutWeb.Tokens.HolderController do
end end
def index(conn, %{"token_id" => address_hash_string}) do def index(conn, %{"token_id" => address_hash_string}) do
options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}]
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, token} <- Chain.token_from_address_hash(address_hash, [{:contract_address, :smart_contract}]) do {:ok, token} <- Chain.token_from_address_hash(address_hash, options) do
render( render(
conn, conn,
"index.html", "index.html",

@ -60,8 +60,10 @@ defmodule BlockScoutWeb.Tokens.InventoryController do
end end
def index(conn, %{"token_id" => address_hash_string}) do def index(conn, %{"token_id" => address_hash_string}) do
options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}]
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, token} <- Chain.token_from_address_hash(address_hash, [{:contract_address, :smart_contract}]) do {:ok, token} <- Chain.token_from_address_hash(address_hash, options) do
render( render(
conn, conn,
"index.html", "index.html",

@ -4,8 +4,10 @@ defmodule BlockScoutWeb.Tokens.ReadContractController do
alias Explorer.{Chain, Market} alias Explorer.{Chain, Market}
def index(conn, %{"token_id" => address_hash_string}) do def index(conn, %{"token_id" => address_hash_string}) do
options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}]
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, token} <- Chain.token_from_address_hash(address_hash, [{:contract_address, :smart_contract}]) do {:ok, token} <- Chain.token_from_address_hash(address_hash, options) do
render( render(
conn, conn,
"index.html", "index.html",

@ -44,8 +44,10 @@ defmodule BlockScoutWeb.Tokens.TransferController do
end end
def index(conn, %{"token_id" => address_hash_string}) do def index(conn, %{"token_id" => address_hash_string}) do
options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}]
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, token} <- Chain.token_from_address_hash(address_hash, [{:contract_address, :smart_contract}]) do {:ok, token} <- Chain.token_from_address_hash(address_hash, options) do
render( render(
conn, conn,
"index.html", "index.html",

@ -36,7 +36,10 @@ defmodule BlockScoutWeb.APIDocsView do
def blockscout_url do def blockscout_url do
if System.get_env("BLOCKSCOUT_HOST") do if System.get_env("BLOCKSCOUT_HOST") do
"http://" <> System.get_env("BLOCKSCOUT_HOST") %URI{host: host, scheme: scheme} = URI.parse(Endpoint.url())
path = System.get_env("NETWORK_PATH") || "/"
scheme <> "://" <> host <> path
else else
Endpoint.url() Endpoint.url()
end end

@ -0,0 +1,25 @@
defmodule BlockScoutWeb.ApiDocsViewTest do
use BlockScoutWeb.ConnCase, async: true
alias BlockScoutWeb.{APIDocsView, Endpoint}
describe "blockscout_url/0" do
test "returns url with scheme and host without port" do
System.put_env("BLOCKSCOUT_HOST", "localhost")
System.put_env("NETWORK_PATH", "")
assert APIDocsView.blockscout_url() == "http://localhost"
assert Endpoint.url() == "http://localhost:4002"
end
test "returns url with scheme and host with path" do
System.put_env("BLOCKSCOUT_HOST", "localhost/chain/dog")
System.put_env("NETWORK_PATH", "/chain/dog")
assert APIDocsView.blockscout_url() == "http://localhost/chain/dog"
assert Endpoint.url() == "http://localhost:4002"
System.put_env("NETWORK_PATH", "")
end
end
end

@ -273,12 +273,12 @@ defmodule Explorer.Chain do
inner_join: transaction in assoc(log, :transaction), inner_join: transaction in assoc(log, :transaction),
order_by: [desc: transaction.block_number, desc: transaction.index], order_by: [desc: transaction.block_number, desc: transaction.index],
preload: [:transaction], preload: [:transaction],
where: where: transaction.block_number < ^block_number,
log.address_hash == ^address_hash and or_where: transaction.block_number == ^block_number and transaction.index > ^transaction_index,
(transaction.block_number < ^block_number or or_where:
(transaction.block_number == ^block_number and transaction.index > ^transaction_index) or transaction.block_number == ^block_number and transaction.index == ^transaction_index and
(transaction.block_number == ^block_number and transaction.index == ^transaction_index and log.index > ^log_index,
log.index > ^log_index)), where: log.address_hash == ^address_hash,
limit: ^paging_options.page_size, limit: ^paging_options.page_size,
select: log select: log
) )
@ -676,31 +676,40 @@ defmodule Explorer.Chain do
iex> Explorer.Chain.hash_to_address(hash) iex> Explorer.Chain.hash_to_address(hash)
{:error, :not_found} {:error, :not_found}
Optionally accepts: ## Options
- a list of bindings to preload, just like `Ecto.Query.preload/3`
- a boolean to also fetch the `has_decompiled_code?` virtual field or not * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is
`:required`, and the `t:Explorer.Chain.Address.t/0` has no associated record for that association,
then the `t:Explorer.Chain.Address.t/0` will not be included in the list.
Optionally it also accepts a boolean to fetch the `has_decompiled_code?` virtual field or not
""" """
@spec hash_to_address(Hash.Address.t(), [Macro.t()], boolean()) :: {:ok, Address.t()} | {:error, :not_found} @spec hash_to_address(Hash.Address.t(), [necessity_by_association_option], boolean()) ::
{:ok, Address.t()} | {:error, :not_found}
def hash_to_address( def hash_to_address(
%Hash{byte_count: unquote(Hash.Address.byte_count())} = hash, %Hash{byte_count: unquote(Hash.Address.byte_count())} = hash,
preloads \\ [ options \\ [
:contracts_creation_internal_transaction, necessity_by_association: %{
:names, :contracts_creation_internal_transaction => :optional,
:smart_contract, :names => :optional,
:token, :smart_contract => :optional,
:contracts_creation_transaction :token => :optional,
:contracts_creation_transaction => :optional
}
], ],
query_decompiled_code_flag \\ true query_decompiled_code_flag \\ true
) do ) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
query = query =
from( from(
address in Address, address in Address,
preload: ^preloads,
where: address.hash == ^hash where: address.hash == ^hash
) )
query query
|> join_associations(necessity_by_association)
|> with_decompiled_code_flag(hash, query_decompiled_code_flag) |> with_decompiled_code_flag(hash, query_decompiled_code_flag)
|> Repo.one() |> Repo.one()
|> case do |> case do
@ -772,16 +781,39 @@ defmodule Explorer.Chain do
iex> {:ok, %Explorer.Chain.Address{hash: found_hash}} = Explorer.Chain.hash_to_address(hash) iex> {:ok, %Explorer.Chain.Address{hash: found_hash}} = Explorer.Chain.hash_to_address(hash)
iex> found_hash == hash iex> found_hash == hash
true true
## Options
* `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is
`:required`, and the `t:Explorer.Chain.Address.t/0` has no associated record for that association,
then the `t:Explorer.Chain.Address.t/0` will not be included in the list.
Optionally it also accepts a boolean to fetch the `has_decompiled_code?` virtual field or not
""" """
@spec find_or_insert_address_from_hash(Hash.Address.t()) :: {:ok, Address.t()} @spec find_or_insert_address_from_hash(Hash.Address.t(), [necessity_by_association_option], boolean()) ::
def find_or_insert_address_from_hash(%Hash{byte_count: unquote(Hash.Address.byte_count())} = hash) do {:ok, Address.t()}
case hash_to_address(hash) do def find_or_insert_address_from_hash(
%Hash{byte_count: unquote(Hash.Address.byte_count())} = hash,
options \\ [
necessity_by_association: %{
:contracts_creation_internal_transaction => :optional,
:names => :optional,
:smart_contract => :optional,
:token => :optional,
:contracts_creation_transaction => :optional
}
],
query_decompiled_code_flag \\ true
) do
case hash_to_address(hash, options, query_decompiled_code_flag) do
{:ok, address} -> {:ok, address} ->
{:ok, address} {:ok, address}
{:error, :not_found} -> {:error, :not_found} ->
create_address(%{hash: to_string(hash)}) create_address(%{hash: to_string(hash)})
hash_to_address(hash) hash_to_address(hash, options, query_decompiled_code_flag)
end end
end end
@ -2720,21 +2752,30 @@ defmodule Explorer.Chain do
@doc """ @doc """
Fetches a `t:Token.t/0` by an address hash. Fetches a `t:Token.t/0` by an address hash.
Optionally accepts a list of bindings to preload, just like `Ecto.Query.preload/3` ## Options
* `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is
`:required`, and the `t:Token.t/0` has no associated record for that association,
then the `t:Token.t/0` will not be included in the list.
""" """
@spec token_from_address_hash(Hash.Address.t(), [Macro.t()]) :: {:ok, Token.t()} | {:error, :not_found} @spec token_from_address_hash(Hash.Address.t(), [necessity_by_association_option]) ::
{:ok, Token.t()} | {:error, :not_found}
def token_from_address_hash( def token_from_address_hash(
%Hash{byte_count: unquote(Hash.Address.byte_count())} = hash, %Hash{byte_count: unquote(Hash.Address.byte_count())} = hash,
preloads \\ [] options \\ []
) do ) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
query = query =
from( from(
token in Token, token in Token,
where: token.contract_address_hash == ^hash, where: token.contract_address_hash == ^hash
preload: ^preloads
) )
case Repo.one(query) do query
|> join_associations(necessity_by_association)
|> Repo.one()
|> case do
nil -> nil ->
{:error, :not_found} {:error, :not_found}

@ -3456,9 +3456,9 @@ defmodule Explorer.ChainTest do
smart_contract = build(:smart_contract) smart_contract = build(:smart_contract)
address = insert(:address, smart_contract: smart_contract) address = insert(:address, smart_contract: smart_contract)
token = insert(:token, contract_address: address) token = insert(:token, contract_address: address)
options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}]
assert {:ok, result} = assert {:ok, result} = Chain.token_from_address_hash(token.contract_address_hash, options)
Chain.token_from_address_hash(token.contract_address_hash, [{:contract_address, :smart_contract}])
assert smart_contract = result.contract_address.smart_contract assert smart_contract = result.contract_address.smart_contract
end end

@ -52,7 +52,9 @@ defmodule Indexer.Fetcher.Token do
@impl BufferedTask @impl BufferedTask
@decorate trace(name: "fetch", resource: "Indexer.Fetcher.Token.run/2", service: :indexer, tracer: Tracer) @decorate trace(name: "fetch", resource: "Indexer.Fetcher.Token.run/2", service: :indexer, tracer: Tracer)
def run([token_contract_address], _json_rpc_named_arguments) do def run([token_contract_address], _json_rpc_named_arguments) do
case Chain.token_from_address_hash(token_contract_address, [{:contract_address, :smart_contract}]) do options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}]
case Chain.token_from_address_hash(token_contract_address, options) do
{:ok, %Token{} = token} -> {:ok, %Token{} = token} ->
catalog_token(token) catalog_token(token)
end end

@ -36,8 +36,10 @@ defmodule Indexer.Fetcher.TokenUpdater do
@doc false @doc false
def update_metadata(token_addresses) when is_list(token_addresses) do def update_metadata(token_addresses) when is_list(token_addresses) do
options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}]
Enum.each(token_addresses, fn address -> Enum.each(token_addresses, fn address ->
case Chain.token_from_address_hash(address, [{:contract_address, :smart_contract}]) do case Chain.token_from_address_hash(address, options) do
{:ok, %Token{cataloged: true} = token} -> {:ok, %Token{cataloged: true} = token} ->
update_metadata(token) update_metadata(token)
end end

Loading…
Cancel
Save