Merge branch 'master' into gs-optimize-current-token-balances

pull/1639/head
Victor Baranov 6 years ago committed by GitHub
commit bc3ba4c7b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      CHANGELOG.md
  2. 3
      apps/block_scout_web/config/config.exs
  3. 79
      apps/block_scout_web/lib/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller.ex
  4. 2
      apps/block_scout_web/lib/block_scout_web/router.ex
  5. 4
      apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification/new.html.eex
  6. 2
      apps/block_scout_web/lib/block_scout_web/views/layout_view.ex
  7. 20
      apps/block_scout_web/priv/gettext/default.pot
  8. 10
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  9. 99
      apps/block_scout_web/test/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller_test.exs
  10. 2
      apps/block_scout_web/test/block_scout_web/views/layout_view_test.exs
  11. 26
      apps/explorer/lib/explorer/chain.ex
  12. 8
      apps/explorer/lib/explorer/chain/block.ex
  13. 33
      apps/explorer/lib/explorer/chain/decompiled_smart_contract.ex
  14. 10
      apps/explorer/lib/explorer/chain/hash.ex
  15. 38
      apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex
  16. 84
      apps/explorer/lib/explorer/chain/import/runner/internal_transactions_indexed_at_blocks.ex
  17. 1
      apps/explorer/lib/explorer/chain/import/stage/address_referencing.ex
  18. 15
      apps/explorer/priv/repo/migrations/20190319081821_create_decompiled_smart_contracts.exs
  19. 11
      apps/explorer/priv/repo/migrations/20190325081658_remove_unique_address_hash_decompiled_contracts.exs
  20. 20
      apps/explorer/test/explorer/chain/decompiled_smart_contract_test.exs
  21. 70
      apps/explorer/test/explorer/chain_test.exs
  22. 17
      apps/explorer/test/support/factory.ex
  23. 34
      apps/indexer/lib/indexer/block/realtime/fetcher.ex
  24. 20
      apps/indexer/lib/indexer/internal_transaction/fetcher.ex
  25. 5
      apps/indexer/lib/indexer/temporary/addresses_without_code.ex
  26. 35
      apps/indexer/test/indexer/internal_transaction/fetcher_test.exs

@ -3,12 +3,17 @@
### Features
- [1611](https://github.com/poanetwork/blockscout/pull/1611) - allow setting the first indexing block
- [1596](https://github.com/poanetwork/blockscout/pull/1596) - add endpoint to create decompiled contracts
### Fixes
- [#1630](https://github.com/poanetwork/blockscout/pull/1630) - (Fix) colour for release link in the footer
- [#1621](https://github.com/poanetwork/blockscout/pull/1621) - Modify query to fetch failed contract creations
- [#1614](https://github.com/poanetwork/blockscout/pull/1614) - Do not fetch burn address token balance
- [#1639](https://github.com/poanetwork/blockscout/pull/1614) - Optimize token holder count updates when importing address current balances
- [#1643](https://github.com/poanetwork/blockscout/pull/1643) - Set internal_transactions_indexed_at for empty blocks
- [#1647](https://github.com/poanetwork/blockscout/pull/1647) - Fix typo in view
### Chore

@ -10,7 +10,8 @@ config :block_scout_web,
namespace: BlockScoutWeb,
ecto_repos: [Explorer.Repo],
version: System.get_env("BLOCKSCOUT_VERSION"),
release_link: System.get_env("RELEASE_LINK")
release_link: System.get_env("RELEASE_LINK"),
decompiled_smart_contract_token: System.get_env("DECOMPILED_SMART_CONTRACT_TOKEN")
config :block_scout_web, BlockScoutWeb.Chain,
network: System.get_env("NETWORK"),

@ -0,0 +1,79 @@
defmodule BlockScoutWeb.API.V1.DecompiledSmartContractController do
use BlockScoutWeb, :controller
alias Explorer.Chain
alias Explorer.Chain.Hash.Address
def create(conn, params) do
if auth_token(conn) == actual_token() do
with {:ok, hash} <- validate_address_hash(params["address_hash"]),
:ok <- smart_contract_exists?(hash),
:ok <- decompiled_contract_exists?(params["address_hash"], params["decompiler_version"]) do
case Chain.create_decompiled_smart_contract(params) do
{:ok, decompiled_smart_contract} ->
send_resp(conn, :created, Jason.encode!(decompiled_smart_contract))
{:error, changeset} ->
errors =
changeset.errors
|> Enum.into(%{}, fn {field, {message, _}} ->
{field, message}
end)
send_resp(conn, :unprocessable_entity, encode(errors))
end
else
:invalid_address ->
send_resp(conn, :unprocessable_entity, encode(%{error: "address_hash is invalid"}))
:not_found ->
send_resp(conn, :unprocessable_entity, encode(%{error: "address is not found"}))
:contract_exists ->
send_resp(
conn,
:unprocessable_entity,
encode(%{error: "decompiled code already exists for the decompiler version"})
)
end
else
send_resp(conn, :forbidden, "")
end
end
defp smart_contract_exists?(address_hash) do
case Chain.hash_to_address(address_hash) do
{:ok, _address} -> :ok
_ -> :not_found
end
end
defp validate_address_hash(address_hash) do
case Address.cast(address_hash) do
{:ok, hash} -> {:ok, hash}
:error -> :invalid_address
end
end
defp decompiled_contract_exists?(address_hash, decompiler_version) do
case Chain.decompiled_code(address_hash, decompiler_version) do
{:ok, _} -> :contract_exists
_ -> :ok
end
end
defp auth_token(conn) do
case get_req_header(conn, "auth_token") do
[token] -> token
other -> other
end
end
defp actual_token do
Application.get_env(:block_scout_web, :decompiled_smart_contract_token)
end
defp encode(data) do
Jason.encode!(data)
end
end

@ -22,6 +22,8 @@ defmodule BlockScoutWeb.Router do
pipe_through(:api)
get("/supply", SupplyController, :supply)
resources("/decompiled_smart_contract", DecompiledSmartContractController, only: [:create])
end
scope "/api", BlockScoutWeb.API.RPC do

@ -28,7 +28,7 @@
</div>
<div class="form-group">
<%= label :evm_version, :evm_version, gettext("EVM Vesion") %>
<%= label :evm_version, :evm_version, gettext("EVM Version") %>
<%= select :evm_version, :evm_version, @evm_versions, class: "form-control", selected: "byzantium", "aria-describedby": "evm-version-help-block" %>
</div>
@ -55,7 +55,7 @@
</div>
<div class="form-group mb-4">
<%= label f, :contructor_arguments, gettext("Enter contructor arguments if the contract had any") %>
<%= label f, :contructor_arguments, gettext("Enter constructor arguments if the contract had any") %>
<%= textarea f, :constructor_arguments, class: "form-control monospace", rows: 3, "aria-describedby": "contract-constructor-arguments-help-block" %>
<%= error_tag f, :constructor_arguments, id: "contract-constructor-arguments-help-block", class: "text-danger", "data-test": "contract-constructor-arguments-error" %>
</div>

@ -82,7 +82,7 @@ defmodule BlockScoutWeb.LayoutView do
if release_link == "" || release_link == nil do
version
else
html_escape({:safe, "<a href=\"#{release_link}\" target=\"_blank\">#{version}</a>"})
html_escape({:safe, "<a href=\"#{release_link}\" class=\"footer-link\" target=\"_blank\">#{version}</a>"})
end
end

@ -1636,11 +1636,6 @@ msgstr ""
msgid "Contract Libraries"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:58
msgid "Enter contructor arguments if the contract had any"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:52
msgid "Last Balance Update: Block #"
@ -1677,11 +1672,6 @@ msgstr ""
msgid "Run"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:31
msgid "EVM Vesion"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:39
msgid ">="
@ -1701,3 +1691,13 @@ msgstr ""
#: lib/block_scout_web/templates/layout/_topnav.html.eex:83
msgid "Search by address, token symbol name, transaction hash, or block number"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:31
msgid "EVM Version"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:58
msgid "Enter constructor arguments if the contract had any"
msgstr ""

@ -1677,11 +1677,6 @@ msgstr ""
msgid "Run"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:31
msgid "EVM Vesion"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:39
msgid ">="
@ -1701,3 +1696,8 @@ msgstr ""
#: lib/block_scout_web/templates/layout/_topnav.html.eex:83
msgid "Search by address, token symbol name, transaction hash, or block number"
msgstr ""
#, elixir-format, fuzzy
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:31
msgid "EVM Version"
msgstr ""

@ -0,0 +1,99 @@
defmodule BlockScoutWeb.API.V1.DecompiledControllerTest do
use BlockScoutWeb.ConnCase
alias Explorer.Repo
alias Explorer.Chain.DecompiledSmartContract
import Ecto.Query,
only: [from: 2]
@secret "secret"
describe "when used authorized" do
setup %{conn: conn} = context do
Application.put_env(:block_scout_web, :decompiled_smart_contract_token, @secret)
auth_conn = conn |> put_req_header("auth_token", @secret)
{:ok, Map.put(context, :conn, auth_conn)}
end
test "returns unprocessable_entity status when params are invalid", %{conn: conn} do
request = post(conn, api_v1_decompiled_smart_contract_path(conn, :create))
assert request.status == 422
assert request.resp_body == "{\"error\":\"address_hash is invalid\"}"
end
test "returns unprocessable_entity when code is empty", %{conn: conn} do
decompiler_version = "test_decompiler"
address = insert(:address)
params = %{
"address_hash" => to_string(address.hash),
"decompiler_version" => decompiler_version
}
request = post(conn, api_v1_decompiled_smart_contract_path(conn, :create), params)
assert request.status == 422
assert request.resp_body == "{\"decompiled_source_code\":\"can't be blank\"}"
end
test "can not update code for the same decompiler version", %{conn: conn} do
address_hash = to_string(insert(:address, hash: "0x0000000000000000000000000000000000000001").hash)
decompiler_version = "test_decompiler"
decompiled_source_code = "hello world"
insert(:decompiled_smart_contract,
address_hash: address_hash,
decompiler_version: decompiler_version,
decompiled_source_code: decompiled_source_code
)
params = %{
"address_hash" => address_hash,
"decompiler_version" => decompiler_version,
"decompiled_source_code" => decompiled_source_code
}
request = post(conn, api_v1_decompiled_smart_contract_path(conn, :create), params)
assert request.status == 422
assert request.resp_body == "{\"error\":\"decompiled code already exists for the decompiler version\"}"
end
test "creates decompiled smart contract", %{conn: conn} do
address_hash = to_string(insert(:address, hash: "0x0000000000000000000000000000000000000001").hash)
decompiler_version = "test_decompiler"
decompiled_source_code = "hello world"
params = %{
"address_hash" => address_hash,
"decompiler_version" => decompiler_version,
"decompiled_source_code" => decompiled_source_code
}
request = post(conn, api_v1_decompiled_smart_contract_path(conn, :create), params)
assert request.status == 201
assert request.resp_body ==
"{\"address_hash\":\"0x0000000000000000000000000000000000000001\",\"decompiler_version\":\"test_decompiler\",\"decompiled_source_code\":\"hello world\"}"
decompiled_smart_contract = Repo.one!(from(d in DecompiledSmartContract, where: d.address_hash == ^address_hash))
assert to_string(decompiled_smart_contract.address_hash) == address_hash
assert decompiled_smart_contract.decompiler_version == decompiler_version
assert decompiled_smart_contract.decompiled_source_code == decompiled_source_code
end
end
describe "when user is not authorized" do
test "returns forbedden", %{conn: conn} do
request = post(conn, api_v1_decompiled_smart_contract_path(conn, :create))
assert request.status == 403
end
end
end

@ -83,7 +83,7 @@ defmodule BlockScoutWeb.LayoutViewTest do
assert LayoutView.release_link("1.3.4") ==
{:safe,
~s(<a href="https://github.com/poanetwork/blockscout/releases/tag/v1.3.4-beta" target="_blank">1.3.4</a>)}
~s(<a href="https://github.com/poanetwork/blockscout/releases/tag/v1.3.4-beta" class="footer-link" target="_blank">1.3.4</a>)}
end
end
end

@ -31,6 +31,7 @@ defmodule Explorer.Chain do
Block,
BlockNumberCache,
Data,
DecompiledSmartContract,
Hash,
Import,
InternalTransaction,
@ -512,6 +513,17 @@ defmodule Explorer.Chain do
|> Repo.insert()
end
@doc """
Creates a decompiled smart contract.
"""
@spec create_decompiled_smart_contract(map()) :: {:ok, Address.t()} | {:error, Ecto.Changeset.t()}
def create_decompiled_smart_contract(attrs) do
%DecompiledSmartContract{}
|> DecompiledSmartContract.changeset(attrs)
|> Repo.insert(on_conflict: :replace_all, conflict_target: [:decompiler_version, :address_hash])
end
@doc """
Converts the `Explorer.Chain.Data.t:t/0` to `iodata` representation that can be written to users efficiently.
@ -656,6 +668,20 @@ defmodule Explorer.Chain do
end
end
def decompiled_code(address_hash, version) do
query =
from(contract in DecompiledSmartContract,
where: contract.address_hash == ^address_hash and contract.decompiler_version == ^version
)
query
|> Repo.one()
|> case do
nil -> {:error, :not_found}
contract -> {:ok, contract.decompiled_source_code}
end
end
@spec token_contract_address_from_token_name(String.t()) :: {:ok, Hash.Address.t()} | {:error, :not_found}
def token_contract_address_from_token_name(name) when is_binary(name) do
query =

@ -106,6 +106,14 @@ defmodule Explorer.Chain.Block do
|> unique_constraint(:hash, name: :blocks_pkey)
end
def number_only_changeset(%__MODULE__{} = block, attrs) do
block
|> cast(attrs, @required_attrs ++ @optional_attrs)
|> validate_required([:number])
|> foreign_key_constraint(:parent_hash)
|> unique_constraint(:hash, name: :blocks_pkey)
end
def blocks_without_reward_query do
from(
b in __MODULE__,

@ -0,0 +1,33 @@
defmodule Explorer.Chain.DecompiledSmartContract do
@moduledoc """
The representation of a decompiled smart contract.
"""
use Explorer.Schema
alias Explorer.Chain.{Address, Hash}
@derive {Jason.Encoder, only: [:address_hash, :decompiler_version, :decompiled_source_code]}
schema "decompiled_smart_contracts" do
field(:decompiler_version, :string)
field(:decompiled_source_code, :string)
belongs_to(
:address,
Address,
foreign_key: :address_hash,
references: :hash,
type: Hash.Address
)
timestamps()
end
def changeset(%__MODULE__{} = smart_contract, attrs) do
smart_contract
|> cast(attrs, [:decompiler_version, :decompiled_source_code, :address_hash])
|> validate_required([:decompiler_version, :decompiled_source_code, :address_hash])
|> unique_constraint(:address_hash)
end
end

@ -233,4 +233,14 @@ defmodule Explorer.Chain.Hash do
|> BitString.encode(options)
end
end
defimpl Jason.Encoder do
alias Jason.Encode
def encode(hash, opts) do
hash
|> to_string()
|> Encode.string(opts)
end
end
end

@ -6,7 +6,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
require Ecto.Query
alias Ecto.{Changeset, Multi, Repo}
alias Explorer.Chain.{Block, Hash, Import, InternalTransaction, Transaction}
alias Explorer.Chain.{Hash, Import, InternalTransaction, Transaction}
alias Explorer.Chain.Import.Runner
import Ecto.Query, only: [from: 2]
@ -54,9 +54,6 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
|> Multi.run(:internal_transactions_indexed_at_transactions, fn repo, _ ->
update_transactions(repo, changes_list, update_transactions_options)
end)
|> Multi.run(:internal_transactions_indexed_at_blocks, fn repo, _ ->
update_blocks(repo, changes_list, update_transactions_options)
end)
end
@impl Runner
@ -195,37 +192,4 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
{:error, %{exception: postgrex_error, transaction_hashes: ordered_transaction_hashes}}
end
end
defp update_blocks(repo, internal_transactions, %{
timeout: timeout,
timestamps: timestamps
})
when is_list(internal_transactions) do
ordered_block_numbers =
internal_transactions
|> MapSet.new(& &1.block_number)
|> Enum.sort()
query =
from(
b in Block,
where: b.number in ^ordered_block_numbers and b.consensus,
update: [
set: [
internal_transactions_indexed_at: ^timestamps.updated_at
]
]
)
block_count = Enum.count(ordered_block_numbers)
try do
{^block_count, result} = repo.update_all(query, [], timeout: timeout)
{:ok, result}
rescue
postgrex_error in Postgrex.Error ->
{:error, %{exception: postgrex_error, block_numbers: ordered_block_numbers}}
end
end
end

@ -0,0 +1,84 @@
defmodule Explorer.Chain.Import.Runner.InternalTransactionsIndexedAtBlocks do
@moduledoc """
Bulk updates `internal_transactions_indexed_at` for provided blocks
"""
require Ecto.Query
alias Ecto.Multi
alias Explorer.Chain.Block
alias Explorer.Chain.Import.Runner
import Ecto.Query, only: [from: 2]
@behaviour Runner
# milliseconds
@timeout 60_000
@type imported :: [%{number: Block.block_number()}]
@impl Runner
def ecto_schema_module, do: Block
@impl Runner
def option_key, do: :internal_transactions_indexed_at_blocks
@impl Runner
def imported_table_row do
%{
value_type: "[%{number: Explorer.Chain.Block.block_number()}]",
value_description: "List of block numbers to set `internal_transactions_indexed_at` field for"
}
end
@impl Runner
def run(multi, changes_list, %{timestamps: timestamps} = options) when is_map(options) do
transactions_timeout = options[Runner.Transactions.option_key()][:timeout] || Runner.Transactions.timeout()
update_transactions_options = %{timeout: transactions_timeout, timestamps: timestamps}
multi
|> Multi.run(:internal_transactions_indexed_at_blocks, fn repo, _ ->
update_blocks(repo, changes_list, update_transactions_options)
end)
end
@impl Runner
def timeout, do: @timeout
defp update_blocks(_repo, [], %{}), do: {:ok, []}
defp update_blocks(repo, block_numbers, %{
timeout: timeout,
timestamps: timestamps
})
when is_list(block_numbers) do
ordered_block_numbers =
block_numbers
|> Enum.map(fn %{number: number} -> number end)
|> Enum.sort()
query =
from(
b in Block,
where: b.number in ^ordered_block_numbers and b.consensus,
update: [
set: [
internal_transactions_indexed_at: ^timestamps.updated_at
]
]
)
block_count = Enum.count(ordered_block_numbers)
try do
{^block_count, result} = repo.update_all(query, [], timeout: timeout)
{:ok, result}
rescue
postgrex_error in Postgrex.Error ->
{:error, %{exception: postgrex_error, block_numbers: ordered_block_numbers}}
end
end
end

@ -19,6 +19,7 @@ defmodule Explorer.Chain.Import.Stage.AddressReferencing do
Runner.Transactions,
Runner.Transaction.Forks,
Runner.InternalTransactions,
Runner.InternalTransactionsIndexedAtBlocks,
Runner.Logs,
Runner.Tokens,
Runner.TokenTransfers,

@ -0,0 +1,15 @@
defmodule Explorer.Repo.Migrations.CreateDecompiledSmartContracts do
use Ecto.Migration
def change do
create table(:decompiled_smart_contracts) do
add(:decompiler_version, :string, null: false)
add(:decompiled_source_code, :text, null: false)
add(:address_hash, references(:addresses, column: :hash, on_delete: :delete_all, type: :bytea), null: false)
timestamps()
end
create(unique_index(:decompiled_smart_contracts, :address_hash))
end
end

@ -0,0 +1,11 @@
defmodule Explorer.Repo.Migrations.RemoveUniqueAddressHashDecompiledContracts do
use Ecto.Migration
def change do
drop(index(:decompiled_smart_contracts, [:address_hash]))
create(
unique_index(:decompiled_smart_contracts, [:address_hash, :decompiler_version], name: :address_decompiler_version)
)
end
end

@ -0,0 +1,20 @@
defmodule Explorer.Chain.DecompiledSmartContractTest do
use Explorer.DataCase
alias Explorer.Chain.DecompiledSmartContract
describe "changeset/2" do
test "with valid attributes" do
params = params_for(:decompiled_smart_contract)
changeset = DecompiledSmartContract.changeset(%DecompiledSmartContract{}, params)
assert changeset.valid?
end
test "with invalid attributes" do
changeset = DecompiledSmartContract.changeset(%DecompiledSmartContract{}, %{elixir: "erlang"})
refute changeset.valid?
end
end
end

@ -14,6 +14,7 @@ defmodule Explorer.ChainTest do
Address,
Block,
Data,
DecompiledSmartContract,
Hash,
InternalTransaction,
Log,
@ -2586,6 +2587,75 @@ defmodule Explorer.ChainTest do
end
end
describe "create_decompiled_smart_contract/1" do
test "with valid params creates decompiled smart contract" do
address_hash = to_string(insert(:address).hash)
decompiler_version = "test_decompiler"
decompiled_source_code = "hello world"
params = %{
address_hash: address_hash,
decompiler_version: decompiler_version,
decompiled_source_code: decompiled_source_code
}
{:ok, decompiled_smart_contract} = Chain.create_decompiled_smart_contract(params)
assert decompiled_smart_contract.decompiler_version == decompiler_version
assert decompiled_smart_contract.decompiled_source_code == decompiled_source_code
assert address_hash == to_string(decompiled_smart_contract.address_hash)
end
test "with invalid params can't create decompiled smart contract" do
params = %{code: "cat"}
{:error, _changeset} = Chain.create_decompiled_smart_contract(params)
end
test "updates smart contract code" do
inserted_decompiled_smart_contract = insert(:decompiled_smart_contract)
code = "code2"
{:ok, _decompiled_smart_contract} =
Chain.create_decompiled_smart_contract(%{
decompiler_version: inserted_decompiled_smart_contract.decompiler_version,
decompiled_source_code: code,
address_hash: inserted_decompiled_smart_contract.address_hash
})
decompiled_smart_contract =
Repo.one(
from(ds in DecompiledSmartContract,
where:
ds.address_hash == ^inserted_decompiled_smart_contract.address_hash and
ds.decompiler_version == ^inserted_decompiled_smart_contract.decompiler_version
)
)
assert decompiled_smart_contract.decompiled_source_code == code
end
test "creates two smart contracts for different decompiler versions" do
inserted_decompiled_smart_contract = insert(:decompiled_smart_contract)
code = "code2"
version = "2"
{:ok, _decompiled_smart_contract} =
Chain.create_decompiled_smart_contract(%{
decompiler_version: version,
decompiled_source_code: code,
address_hash: inserted_decompiled_smart_contract.address_hash
})
decompiled_smart_contracts =
Repo.all(
from(ds in DecompiledSmartContract, where: ds.address_hash == ^inserted_decompiled_smart_contract.address_hash)
)
assert Enum.count(decompiled_smart_contracts) == 2
end
end
describe "create_smart_contract/1" do
setup do
smart_contract_bytecode =

@ -19,6 +19,7 @@ defmodule Explorer.Factory do
Block,
ContractMethod,
Data,
DecompiledSmartContract,
Hash,
InternalTransaction,
Log,
@ -509,7 +510,7 @@ defmodule Explorer.Factory do
}
end
def smart_contract_factory() do
def smart_contract_factory do
contract_code_info = contract_code_info()
%SmartContract{
@ -522,7 +523,17 @@ defmodule Explorer.Factory do
}
end
def token_balance_factory() do
def decompiled_smart_contract_factory do
contract_code_info = contract_code_info()
%DecompiledSmartContract{
address_hash: insert(:address, contract_code: contract_code_info.bytecode).hash,
decompiler_version: "test_decompiler",
decompiled_source_code: contract_code_info.source_code
}
end
def token_balance_factory do
%TokenBalance{
address: build(:address),
token_contract_address_hash: insert(:token).contract_address_hash,
@ -532,7 +543,7 @@ defmodule Explorer.Factory do
}
end
def address_current_token_balance_factory() do
def address_current_token_balance_factory do
%CurrentTokenBalance{
address: build(:address),
token_contract_address_hash: insert(:token).contract_address_hash,

@ -175,7 +175,8 @@ defmodule Indexer.Block.Realtime.Fetcher do
{:ok,
%{
addresses_params: internal_transactions_addresses_params,
internal_transactions_params: internal_transactions_params
internal_transactions_params: internal_transactions_params,
internal_transactions_indexed_at_blocks_params: internal_transactions_indexed_at_blocks_params
}}} <-
{:internal_transactions,
internal_transactions(block_fetcher, %{
@ -204,7 +205,11 @@ defmodule Indexer.Block.Realtime.Fetcher do
|> put_in([Access.key(:address_coin_balances, %{}), :params], balances_params)
|> put_in([Access.key(:address_current_token_balances, %{}), :params], address_current_token_balances)
|> put_in([Access.key(:address_token_balances), :params], address_token_balances)
|> put_in([Access.key(:internal_transactions, %{}), :params], internal_transactions_params),
|> put_in([Access.key(:internal_transactions, %{}), :params], internal_transactions_params)
|> put_in([:internal_transactions_indexed_at_blocks], %{
params: internal_transactions_indexed_at_blocks_params,
with: :number_only_changeset
}),
{:import, {:ok, imported} = ok} <- {:import, Chain.import(chain_import_options)} do
async_import_remaining_block_data(imported, %{block_rewards: %{errors: block_reward_errors}})
ok
@ -370,8 +375,15 @@ defmodule Indexer.Block.Realtime.Fetcher do
transactions_params: transactions_params
}
) do
json_rpc_named_arguments
|> Keyword.fetch!(:variant)
variant = Keyword.fetch!(json_rpc_named_arguments, :variant)
internal_transactions_indexed_at_blocks_params =
case variant do
EthereumJSONRPC.Parity -> blocks_params
_ -> []
end
variant
|> case do
EthereumJSONRPC.Parity ->
blocks_params
@ -391,10 +403,20 @@ defmodule Indexer.Block.Realtime.Fetcher do
|> Kernel.++(addresses_params)
|> AddressExtraction.merge_addresses()
{:ok, %{addresses_params: merged_addresses_params, internal_transactions_params: internal_transactions_params}}
{:ok,
%{
addresses_params: merged_addresses_params,
internal_transactions_params: internal_transactions_params,
internal_transactions_indexed_at_blocks_params: internal_transactions_indexed_at_blocks_params
}}
:ignore ->
{:ok, %{addresses_params: addresses_params, internal_transactions_params: []}}
{:ok,
%{
addresses_params: addresses_params,
internal_transactions_params: [],
internal_transactions_indexed_at_blocks_params: []
}}
{:error, _reason} = error ->
error

@ -134,6 +134,10 @@ defmodule Indexer.InternalTransaction.Fetcher do
block_number
end
defp block_params(block_number) when is_integer(block_number) do
%{number: block_number}
end
@impl BufferedTask
@decorate trace(
name: "fetch",
@ -144,10 +148,12 @@ defmodule Indexer.InternalTransaction.Fetcher do
def run(entries, json_rpc_named_arguments) do
variant = Keyword.fetch!(json_rpc_named_arguments, :variant)
unique_entries =
unique_entries = unique_entries(entries, variant)
internal_transactions_indexed_at_blocks =
case variant do
EthereumJSONRPC.Parity -> Enum.uniq(entries)
_ -> unique_entries(entries)
EthereumJSONRPC.Parity -> Enum.map(unique_entries, &block_params/1)
_ -> []
end
unique_entries_count = Enum.count(unique_entries)
@ -184,6 +190,10 @@ defmodule Indexer.InternalTransaction.Fetcher do
Chain.import(%{
addresses: %{params: addresses_params},
internal_transactions: %{params: internal_transactions_params_without_failed_creations},
internal_transactions_indexed_at_blocks: %{
params: internal_transactions_indexed_at_blocks,
with: :number_only_changeset
},
timeout: :infinity
}) do
async_import_coin_balances(imported, %{
@ -219,8 +229,10 @@ defmodule Indexer.InternalTransaction.Fetcher do
end
end
defp unique_entries(entries, EthereumJSONRPC.Parity), do: Enum.uniq(entries)
# Protection and improved reporting for https://github.com/poanetwork/blockscout/issues/289
defp unique_entries(entries) do
defp unique_entries(entries, _) do
entries_by_hash_bytes = Enum.group_by(entries, &elem(&1, 1))
if map_size(entries_by_hash_bytes) < length(entries) do

@ -16,6 +16,7 @@ defmodule Indexer.Temporary.AddressesWithoutCode do
@task_options [max_concurrency: 3, timeout: :infinity]
@batch_size 500
@query_timeout :infinity
def start_link([fetcher, gen_server_options]) do
GenServer.start_link(__MODULE__, fetcher, gen_server_options)
@ -104,7 +105,7 @@ defmodule Indexer.Temporary.AddressesWithoutCode do
end
defp process_query(query, fetcher) do
query_stream = Repo.stream(query, max_rows: @batch_size)
query_stream = Repo.stream(query, max_rows: @batch_size, timeout: @query_timeout)
stream =
TaskSupervisor
@ -114,7 +115,7 @@ defmodule Indexer.Temporary.AddressesWithoutCode do
@task_options
)
Repo.transaction(fn -> Stream.run(stream) end)
Repo.transaction(fn -> Stream.run(stream) end, timeout: @query_timeout)
end
def refetch_block(block, fetcher) do

@ -76,6 +76,41 @@ defmodule Indexer.InternalTransaction.FetcherTest do
assert :ok = InternalTransaction.Fetcher.run(hash_strings, json_rpc_named_arguments)
end
@tag :no_geth
test "marks a block indexed even if no internal transactions are fetched", %{
json_rpc_named_arguments: json_rpc_named_arguments
} do
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
case Keyword.fetch!(json_rpc_named_arguments, :variant) do
EthereumJSONRPC.Parity ->
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [%{id: id}], _options ->
{:ok,
[
%{
id: id,
result: []
}
]}
end)
variant_name ->
raise ArgumentError, "Unsupported variant name (#{variant_name})"
end
end
block_number = 1_000_006
insert(:block, number: block_number)
assert :ok = InternalTransaction.Fetcher.run([block_number], json_rpc_named_arguments)
assert InternalTransaction.Fetcher.init(
[],
fn block_number, acc -> [block_number | acc] end,
json_rpc_named_arguments
) == []
end
describe "init/2" do
test "does not buffer pending transactions", %{json_rpc_named_arguments: json_rpc_named_arguments} do
insert(:transaction)

Loading…
Cancel
Save