Merge branch 'master' into feature/#1476-add-styles-for-POSDAO-network

* master: (28 commits)
  feat: verify contracts via an RPC endpoint
  Remove obsolete InvalidConsensus.Worker
  Discard child block with parent_hash not matching hash of imported block
  Expand non-consensus block regression test to test for race conditions
  Remove obsolete ConsensusEnsurer
  feat: add not_decompiled_with_version filter
  Update fetcher_test.exs
  mix format
  fix test
  update gettext
  fix build
  remove fetching token 2 times
  update CHANGELOG
  update tokens in fetcher
  add CHANGELOG entry
  exclude decompiled smart contract from encoding
  add CHANGELOG entry
  update metadata in controller
  add CHANGELOG entry
  decrease token metadata update interval
  ...
pull/1704/head
Gabriel Rodriguez Alsina 6 years ago
commit ee635fa42a
  1. 10
      CHANGELOG.md
  2. 15
      apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex
  3. 107
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex
  4. 3
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/rpc_translator.ex
  5. 172
      apps/block_scout_web/lib/block_scout_web/etherscan.ex
  6. 5
      apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification/new.html.eex
  7. 28
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex
  8. 51
      apps/block_scout_web/priv/gettext/default.pot
  9. 53
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  10. 151
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs
  11. 13
      apps/block_scout_web/test/support/fixture/smart_contract/compiler_tests.json
  12. 1
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex
  13. 72
      apps/explorer/lib/explorer/chain.ex
  14. 2
      apps/explorer/lib/explorer/chain/address.ex
  15. 28
      apps/explorer/lib/explorer/chain/import/runner/blocks.ex
  16. 6
      apps/explorer/lib/explorer/chain/smart_contract.ex
  17. 2
      apps/explorer/lib/explorer/chain/transaction_count_cache.ex
  18. 27
      apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex
  19. 23
      apps/explorer/lib/explorer/smart_contract/verifier.ex
  20. 9
      apps/explorer/priv/compile_solc.js
  21. 38
      apps/explorer/test/explorer/chain/import/runner/blocks_test.exs
  22. 2
      apps/explorer/test/explorer/chain/transaction_count_cache_test.exs
  23. 21
      apps/explorer/test/explorer/chain_test.exs
  24. 49
      apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs
  25. 2
      apps/indexer/config/config.exs
  26. 45
      apps/indexer/lib/indexer/block/invalid_consensus/supervisor.ex
  27. 99
      apps/indexer/lib/indexer/block/invalid_consensus/worker.ex
  28. 34
      apps/indexer/lib/indexer/block/realtime/consensus_ensurer.ex
  29. 9
      apps/indexer/lib/indexer/block/realtime/fetcher.ex
  30. 3
      apps/indexer/lib/indexer/block/supervisor.ex
  31. 7
      apps/indexer/lib/indexer/token/fetcher.ex
  32. 87
      apps/indexer/test/indexer/block/invalid_consensus/worker_test.exs
  33. 6
      apps/indexer/test/indexer/token/fetcher_test.exs

@ -2,12 +2,18 @@
### Features ### Features
- [1654](https://github.com/poanetwork/blockscout/pull/1654) - add decompiled code tab - [#1662](https://github.com/poanetwork/blockscout/pull/1662) - allow specifying number of optimization runs
- [1661](https://github.com/poanetwork/blockscout/pull/1661) - try to compile smart contract with the latest evm version - [#1654](https://github.com/poanetwork/blockscout/pull/1654) - add decompiled code tab
- [#1661](https://github.com/poanetwork/blockscout/pull/1661) - try to compile smart contract with the latest evm version
- [#1665](https://github.com/poanetwork/blockscout/pull/1665) - Add contract verification RPC endpoint.
### Fixes ### Fixes
- [#1669](https://github.com/poanetwork/blockscout/pull/1669) - do not fail if multiple matching tokens are found - [#1669](https://github.com/poanetwork/blockscout/pull/1669) - do not fail if multiple matching tokens are found
- [#1691](https://github.com/poanetwork/blockscout/pull/1691) - decrease token metadata update interval
- [#1688](https://github.com/poanetwork/blockscout/pull/1688) - do not fail if failure reason is atom
- [#1692](https://github.com/poanetwork/blockscout/pull/1692) - exclude decompiled smart contract from encoding
- [#1684](https://github.com/poanetwork/blockscout/pull/1684) - Discard child block with parent_hash not matching hash of imported block
### Chore ### Chore

@ -26,10 +26,14 @@ defmodule BlockScoutWeb.AddressContractVerificationController do
"address_id" => address_hash_string, "address_id" => address_hash_string,
"smart_contract" => smart_contract, "smart_contract" => smart_contract,
"external_libraries" => external_libraries, "external_libraries" => external_libraries,
"evm_version" => evm_version "evm_version" => evm_version,
"optimization" => optimization
} }
) do ) do
smart_sontact_with_evm_version = Map.put(smart_contract, "evm_version", evm_version["evm_version"]) smart_sontact_with_evm_version =
smart_contract
|> Map.put("evm_version", evm_version["evm_version"])
|> Map.put("optimization_runs", parse_optimization_runs(optimization))
case Publisher.publish(address_hash_string, smart_sontact_with_evm_version, external_libraries) do case Publisher.publish(address_hash_string, smart_sontact_with_evm_version, external_libraries) do
{:ok, _smart_contract} -> {:ok, _smart_contract} ->
@ -45,4 +49,11 @@ defmodule BlockScoutWeb.AddressContractVerificationController do
) )
end end
end end
def parse_optimization_runs(%{"runs" => runs}) do
case Integer.parse(runs) do
{integer, ""} -> integer
_ -> 200
end
end
end end

@ -4,10 +4,28 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
alias BlockScoutWeb.API.RPC.Helpers alias BlockScoutWeb.API.RPC.Helpers
alias Explorer.Chain alias Explorer.Chain
alias Explorer.Chain.SmartContract alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.Publisher
def verify(conn, %{"addressHash" => address_hash} = params) do
with {:params, {:ok, fetched_params}} <- {:params, fetch_verify_params(params)},
{:params, external_libraries} <-
{:params, fetch_external_libraries(params)},
{:publish, {:ok, smart_contract}} <-
{:publish, Publisher.publish(address_hash, fetched_params, external_libraries)},
preloaded_smart_contract <- SmartContract.preload_decompiled_smart_contract(smart_contract) do
render(conn, :verify, %{contract: preloaded_smart_contract, address_hash: address_hash})
else
{:publish, _} ->
render(conn, :error, error: "Something went wrong while publishing the contract.")
{:params, {:error, error}} ->
render(conn, :error, error: error)
end
end
def listcontracts(conn, params) do def listcontracts(conn, params) do
with pagination_options <- Helpers.put_pagination_options(%{}, params), with pagination_options <- Helpers.put_pagination_options(%{}, params),
{:params, {:ok, options}} <- {:params, add_filter(pagination_options, params)} do {:params, {:ok, options}} <- {:params, add_filters(pagination_options, params)} do
options_with_defaults = options_with_defaults =
options options
|> Map.put_new(:page_number, 0) |> Map.put_new(:page_number, 0)
@ -71,7 +89,8 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
Chain.list_verified_contracts(page_size, offset) Chain.list_verified_contracts(page_size, offset)
:decompiled -> :decompiled ->
Chain.list_decompiled_contracts(page_size, offset) not_decompiled_with_version = Map.get(opts, :not_decompiled_with_version)
Chain.list_decompiled_contracts(page_size, offset, not_decompiled_with_version)
:unverified -> :unverified ->
Chain.list_unverified_contracts(page_size, offset) Chain.list_unverified_contracts(page_size, offset)
@ -84,6 +103,12 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
end end
end end
defp add_filters(options, params) do
options
|> add_filter(params)
|> add_not_decompiled_with_version(params)
end
defp add_filter(options, params) do defp add_filter(options, params) do
with {:param, {:ok, value}} <- {:param, Map.fetch(params, "filter")}, with {:param, {:ok, value}} <- {:param, Map.fetch(params, "filter")},
{:validation, {:ok, filter}} <- {:validation, contracts_filter(value)} do {:validation, {:ok, filter}} <- {:validation, contracts_filter(value)} do
@ -94,6 +119,17 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
end end
end end
defp add_not_decompiled_with_version({:ok, options}, params) do
case Map.fetch(params, "not_decompiled_with_version") do
{:ok, value} -> {:ok, Map.put(options, :not_decompiled_with_version, value)}
:error -> {:ok, options}
end
end
defp add_not_decompiled_with_version(options, _params) do
options
end
defp contracts_filter(nil), do: {:ok, nil} defp contracts_filter(nil), do: {:ok, nil}
defp contracts_filter(1), do: {:ok, :verified} defp contracts_filter(1), do: {:ok, :verified}
defp contracts_filter(2), do: {:ok, :decompiled} defp contracts_filter(2), do: {:ok, :decompiled}
@ -137,4 +173,71 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
{:contract, result} {:contract, result}
end end
defp fetch_verify_params(params) do
{:ok, %{}}
|> required_param(params, "addressHash", "address_hash")
|> required_param(params, "name", "name")
|> required_param(params, "compilerVersion", "compiler_version")
|> required_param(params, "optimization", "optimization")
|> required_param(params, "contractSourceCode", "contract_source_code")
|> optional_param(params, "evmVersion", "evm_version")
|> optional_param(params, "constructorArguments", "constructor_arguments")
|> optional_param(params, "optimizationRuns", "optimization_runs")
|> parse_optimization_runs()
end
defp parse_optimization_runs({:ok, %{"optimization_runs" => runs} = opts}) when is_bitstring(runs) do
{:ok, Map.put(opts, "optimization_runs", 200)}
end
defp parse_optimization_runs({:ok, %{"optimization_runs" => runs} = opts}) when is_integer(runs) do
{:ok, opts}
end
defp parse_optimization_runs({:ok, opts}) do
{:ok, Map.put(opts, "optimization_runs", 200)}
end
defp parse_optimization_runs(other), do: other
defp fetch_external_libraries(params) do
Enum.reduce(1..5, %{}, fn number, acc ->
case Map.fetch(params, "library#{number}Name") do
{:ok, library_name} ->
library_address = Map.get(params, "library#{number}Address")
acc
|> Map.put("library#{number}_name", library_name)
|> Map.put("library#{number}_address", library_address)
:error ->
acc
end
end)
end
defp required_param({:error, _} = error, _, _, _), do: error
defp required_param({:ok, map}, params, key, new_key) do
case Map.fetch(params, key) do
{:ok, value} ->
{:ok, Map.put(map, new_key, value)}
:error ->
{:error, "#{key} is required."}
end
end
defp optional_param({:error, _} = error, _, _, _), do: error
defp optional_param({:ok, map}, params, key, new_key) do
case Map.fetch(params, key) do
{:ok, value} ->
{:ok, Map.put(map, new_key, value)}
:error ->
{:ok, map}
end
end
end end

@ -66,6 +66,7 @@ defmodule BlockScoutWeb.API.RPC.RPCTranslator do
def call_controller(conn, controller, action) do def call_controller(conn, controller, action) do
{:ok, controller.call(conn, action)} {:ok, controller.call(conn, action)}
rescue rescue
Conn.WrapperError -> :error Conn.WrapperError ->
:error
end end
end end

@ -328,6 +328,49 @@ defmodule BlockScoutWeb.Etherscan do
"result" => nil "result" => nil
} }
@contract_verify_example_value %{
"status" => "1",
"message" => "OK",
"result" => %{
"SourceCode" => """
pragma solidity >0.4.24;
contract Test {
constructor() public { b = hex"12345678901234567890123456789012"; }
event Event(uint indexed a, bytes32 b);
event Event2(uint indexed a, bytes32 b);
function foo(uint a) public { emit Event(a, b); }
bytes32 b;
}
""",
"ABI" => """
[{
"type":"event",
"inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}],
"name":"Event"
}, {
"type":"event",
"inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}],
"name":"Event2"
}, {
"type":"function",
"inputs": [{"name":"a","type":"uint256"}],
"name":"foo",
"outputs": []
}]
""",
"ContractName" => "Test",
"CompilerVersion" => "v0.2.1-2016-01-30-91a6b35",
"OptimizationUsed" => "1"
}
}
@contract_verify_example_value_error %{
"status" => "0",
"message" => "There was an error verifying the contract.",
"result" => nil
}
@contract_getsourcecode_example_value %{ @contract_getsourcecode_example_value %{
"status" => "1", "status" => "1",
"message" => "OK", "message" => "OK",
@ -1719,6 +1762,12 @@ defmodule BlockScoutWeb.Etherscan do
type: "string", type: "string",
description: description:
"verified|decompiled|unverified|not_decompiled, or 1|2|3|4 respectively. This requests only contracts with that status." "verified|decompiled|unverified|not_decompiled, or 1|2|3|4 respectively. This requests only contracts with that status."
},
%{
key: "not_decompiled_with_version",
type: "string",
description:
"Ensures that none of the returned contracts were decompiled with the provided version. Ignored unless filtering for decompiled contracts."
} }
], ],
responses: [ responses: [
@ -1741,6 +1790,126 @@ defmodule BlockScoutWeb.Etherscan do
] ]
} }
@contract_verify_action %{
name: "verify",
description: "Verify a contract with its source code and contract creation information.",
required_params: [
%{
key: "addressHash",
placeholder: "addressHash",
type: "string",
description: "The address of the contract."
},
%{
key: "name",
placeholder: "name",
type: "string",
description: "The name of the contract."
},
%{
key: "compilerVersion",
placeholder: "compilerVersion",
type: "string",
description: "The compiler version for the contract."
},
%{
key: "optimization",
placeholder: false,
type: "boolean",
description: "Whether or not compiler optimizations were enabled."
},
%{
key: "contractSourceCode",
placeholder: "contractSourceCode",
type: "string",
description: "The source code of the contract."
}
],
optional_params: [
%{
key: "constructorArguments",
type: "string",
description: "The constructor argument data provided."
},
%{
key: "evmVersion",
placeholder: "evmVersion",
type: "string",
description: "The EVM version for the contract."
},
%{
key: "optimizationRuns",
placeholder: "optimizationRuns",
type: "integer",
description: "The number of optimization runs used during compilation"
},
%{
key: "library1Name",
type: "string",
description: "The name of the first library used."
},
%{
key: "library1Address",
type: "string",
description: "The address of the first library used."
},
%{
key: "library2Name",
type: "string",
description: "The name of the second library used."
},
%{
key: "library2Address",
type: "string",
description: "The address of the second library used."
},
%{
key: "library3Name",
type: "string",
description: "The name of the third library used."
},
%{
key: "library3Address",
type: "string",
description: "The address of the third library used."
},
%{
key: "library4Name",
type: "string",
description: "The name of the fourth library used."
},
%{
key: "library4Address",
type: "string",
description: "The address of the fourth library used."
},
%{
key: "library5Name",
type: "string",
description: "The name of the fourth library used."
},
%{
key: "library5Address",
type: "string",
description: "The address of the fourth library used."
}
],
responses: [
%{
code: "200",
description: "successful operation",
example_value: Jason.encode!(@contract_verify_example_value),
type: "model",
model: @contract_model
},
%{
code: "200",
description: "error",
example_value: Jason.encode!(@contract_verify_example_value_error)
}
]
}
@contract_getabi_action %{ @contract_getabi_action %{
name: "getabi", name: "getabi",
description: "Get ABI for verified contract. Also available through a GraphQL 'addresses' query.", description: "Get ABI for verified contract. Also available through a GraphQL 'addresses' query.",
@ -1970,7 +2139,8 @@ defmodule BlockScoutWeb.Etherscan do
actions: [ actions: [
@contract_listcontracts_action, @contract_listcontracts_action,
@contract_getabi_action, @contract_getabi_action,
@contract_getsourcecode_action @contract_getsourcecode_action,
@contract_verify_action
] ]
} }

@ -48,6 +48,11 @@
<%= error_tag f, :optimization, id: "optimization-help-block", class: "text-danger" %> <%= error_tag f, :optimization, id: "optimization-help-block", class: "text-danger" %>
</div> </div>
<div class="form-group">
<%= label f, :name, gettext("Optimization runs") %>
<%= text_input :optimization, :runs, value: 200, class: "form-control", "aria-describedby": "optimization-runs-help-block", "data-test": "optimization-runs" %>
</div>
<div class="form-group mb-4"> <div class="form-group mb-4">
<%= label f, :contract_source_code, gettext("Enter the Solidity Contract Code below") %> <%= label f, :contract_source_code, gettext("Enter the Solidity Contract Code below") %>
<%= textarea f, :contract_source_code, class: "form-control monospace", rows: 3, "aria-describedby": "contract-source-code-help-block" %> <%= textarea f, :contract_source_code, class: "form-control monospace", rows: 3, "aria-describedby": "contract-source-code-help-block" %>

@ -22,6 +22,10 @@ defmodule BlockScoutWeb.API.RPC.ContractView do
RPCView.render("error.json", assigns) RPCView.render("error.json", assigns)
end end
def render("verify.json", %{contract: contract, address_hash: address_hash}) do
RPCView.render("show.json", data: prepare_source_code_contract(contract, address_hash))
end
defp prepare_source_code_contract(nil, address_hash) do defp prepare_source_code_contract(nil, address_hash) do
%{ %{
"Address" => to_string(address_hash), "Address" => to_string(address_hash),
@ -36,19 +40,27 @@ defmodule BlockScoutWeb.API.RPC.ContractView do
end end
defp prepare_source_code_contract(contract, _) do defp prepare_source_code_contract(contract, _) do
decompiled_smart_contract = latest_decompiled_smart_contract(contract.decompiled_smart_contracts)
%{ %{
"Address" => to_string(contract.address_hash), "Address" => to_string(contract.address_hash),
"SourceCode" => contract.contract_source_code, "SourceCode" => contract.contract_source_code,
"ABI" => Jason.encode!(contract.abi), "ABI" => Jason.encode!(contract.abi),
"ContractName" => contract.name, "ContractName" => contract.name,
"DecompiledSourceCode" => decompiled_source_code(contract.decompiled_smart_contract), "DecompiledSourceCode" => decompiled_source_code(decompiled_smart_contract),
"DecompilerVersion" => decompiler_version(contract.decompiled_smart_contract), "DecompilerVersion" => decompiler_version(decompiled_smart_contract),
"CompilerVersion" => contract.compiler_version, "CompilerVersion" => contract.compiler_version,
"OptimizationUsed" => if(contract.optimization, do: "1", else: "0") "OptimizationUsed" => if(contract.optimization, do: "1", else: "0")
} }
end end
defp prepare_contract(%Address{hash: hash, smart_contract: nil, decompiled_smart_contract: decompiled_smart_contract}) do defp prepare_contract(%Address{
hash: hash,
smart_contract: nil,
decompiled_smart_contracts: decompiled_smart_contracts
}) do
decompiled_smart_contract = latest_decompiled_smart_contract(decompiled_smart_contracts)
%{ %{
"Address" => to_string(hash), "Address" => to_string(hash),
"SourceCode" => "", "SourceCode" => "",
@ -64,8 +76,10 @@ defmodule BlockScoutWeb.API.RPC.ContractView do
defp prepare_contract(%Address{ defp prepare_contract(%Address{
hash: hash, hash: hash,
smart_contract: %SmartContract{} = contract, smart_contract: %SmartContract{} = contract,
decompiled_smart_contract: decompiled_smart_contract decompiled_smart_contracts: decompiled_smart_contracts
}) do }) do
decompiled_smart_contract = latest_decompiled_smart_contract(decompiled_smart_contracts)
%{ %{
"Address" => to_string(hash), "Address" => to_string(hash),
"SourceCode" => contract.contract_source_code, "SourceCode" => contract.contract_source_code,
@ -78,6 +92,12 @@ defmodule BlockScoutWeb.API.RPC.ContractView do
} }
end end
defp latest_decompiled_smart_contract([]), do: nil
defp latest_decompiled_smart_contract(contracts) do
Enum.max_by(contracts, fn contract -> DateTime.to_unix(contract.inserted_at) end)
end
defp decompiled_source_code(nil), do: "Contract source code not decompiled." defp decompiled_source_code(nil), do: "Contract source code not decompiled."
defp decompiled_source_code(%DecompiledSmartContract{decompiled_source_code: decompiled_source_code}) do defp decompiled_source_code(%DecompiledSmartContract{decompiled_source_code: decompiled_source_code}) do

@ -197,7 +197,7 @@ msgid "Blocks Validated"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:130 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:135
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:24 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:24
msgid "Cancel" msgid "Cancel"
msgstr "" msgstr ""
@ -367,7 +367,7 @@ msgid "ETH"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:52 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:57
msgid "Enter the Solidity Contract Code below" msgid "Enter the Solidity Contract Code below"
msgstr "" msgstr ""
@ -713,7 +713,7 @@ msgid "Request URL"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:128 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:133
msgid "Reset" msgid "Reset"
msgstr "" msgstr ""
@ -1024,7 +1024,7 @@ msgid "Verify & Publish"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:127 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:132
msgid "Verify & publish" msgid "Verify & publish"
msgstr "" msgstr ""
@ -1165,7 +1165,7 @@ msgid "Loading..."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:125 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:130
msgid "Loading...." msgid "Loading...."
msgstr "" msgstr ""
@ -1582,57 +1582,57 @@ msgid "Genesis Block"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:71 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:76
msgid "1 Library Address" msgid "1 Library Address"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:66 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:71
msgid "1 Library Name" msgid "1 Library Name"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:81 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:86
msgid "2 Library Address" msgid "2 Library Address"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:76 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:81
msgid "2 Library Name" msgid "2 Library Name"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:91 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:96
msgid "3 Library Address" msgid "3 Library Address"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:86 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:91
msgid "3 Library Name" msgid "3 Library Name"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:101 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:106
msgid "4 Library Address" msgid "4 Library Address"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:96 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:101
msgid "4 Library Name" msgid "4 Library Name"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:111 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:116
msgid "5 Library Address" msgid "5 Library Address"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:106 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:111
msgid "5 Library Name" msgid "5 Library Name"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:63 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:68
msgid "Contract Libraries" msgid "Contract Libraries"
msgstr "" msgstr ""
@ -1698,16 +1698,10 @@ msgid "EVM Version"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:58 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:63
msgid "Enter constructor arguments if the contract had any" msgid "Enter constructor arguments if the contract had any"
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:67
#: lib/block_scout_web/templates/address/_tabs.html.eex:133
msgid "Decompiled code"
msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:20 #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:20
msgid "Copy Decompiled Contract Code" msgid "Copy Decompiled Contract Code"
@ -1718,6 +1712,12 @@ msgstr ""
msgid "Decompiled Code" msgid "Decompiled Code"
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:67
#: lib/block_scout_web/templates/address/_tabs.html.eex:133
msgid "Decompiled code"
msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:18 #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:18
msgid "Decompiled contract code" msgid "Decompiled contract code"
@ -1727,3 +1727,8 @@ msgstr ""
#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:11 #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:11
msgid "Decompiler version" msgid "Decompiler version"
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:52
msgid "Optimization runs"
msgstr ""

@ -197,7 +197,7 @@ msgid "Blocks Validated"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:130 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:135
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:24 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:24
msgid "Cancel" msgid "Cancel"
msgstr "" msgstr ""
@ -367,7 +367,7 @@ msgid "ETH"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:52 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:57
msgid "Enter the Solidity Contract Code below" msgid "Enter the Solidity Contract Code below"
msgstr "" msgstr ""
@ -713,7 +713,7 @@ msgid "Request URL"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:128 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:133
msgid "Reset" msgid "Reset"
msgstr "" msgstr ""
@ -1024,7 +1024,7 @@ msgid "Verify & Publish"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:127 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:132
msgid "Verify & publish" msgid "Verify & publish"
msgstr "" msgstr ""
@ -1165,7 +1165,7 @@ msgid "Loading..."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:125 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:130
msgid "Loading...." msgid "Loading...."
msgstr "" msgstr ""
@ -1582,57 +1582,57 @@ msgid "Genesis Block"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:71 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:76
msgid "1 Library Address" msgid "1 Library Address"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:66 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:71
msgid "1 Library Name" msgid "1 Library Name"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:81 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:86
msgid "2 Library Address" msgid "2 Library Address"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:76 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:81
msgid "2 Library Name" msgid "2 Library Name"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:91 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:96
msgid "3 Library Address" msgid "3 Library Address"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:86 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:91
msgid "3 Library Name" msgid "3 Library Name"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:101 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:106
msgid "4 Library Address" msgid "4 Library Address"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:96 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:101
msgid "4 Library Name" msgid "4 Library Name"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:111 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:116
msgid "5 Library Address" msgid "5 Library Address"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:106 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:111
msgid "5 Library Name" msgid "5 Library Name"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:63 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:68
msgid "Contract Libraries" msgid "Contract Libraries"
msgstr "" msgstr ""
@ -1698,16 +1698,10 @@ msgid "EVM Version"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:58 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:63
msgid "Enter constructor arguments if the contract had any" msgid "Enter constructor arguments if the contract had any"
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:67
#: lib/block_scout_web/templates/address/_tabs.html.eex:133
msgid "Decompiled code"
msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:20 #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:20
msgid "Copy Decompiled Contract Code" msgid "Copy Decompiled Contract Code"
@ -1718,12 +1712,23 @@ msgstr ""
msgid "Decompiled Code" msgid "Decompiled Code"
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:67
#: lib/block_scout_web/templates/address/_tabs.html.eex:133
msgid "Decompiled code"
msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:18 #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:18
msgid "Decompiled contract code" msgid "Decompiled contract code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format, fuzzy
#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:11 #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:11
msgid "Decompiler version" msgid "Decompiler version"
msgstr "" msgstr ""
#, elixir-format, fuzzy
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:52
msgid "Optimization runs"
msgstr ""

@ -1,5 +1,6 @@
defmodule BlockScoutWeb.API.RPC.ContractControllerTest do defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
use BlockScoutWeb.ConnCase use BlockScoutWeb.ConnCase
alias Explorer.Factory
describe "listcontracts" do describe "listcontracts" do
setup do setup do
@ -157,6 +158,60 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
] ]
end end
test "filtering for only decompiled contracts, with a decompiled with version filter", %{params: params, conn: conn} do
insert(:decompiled_smart_contract, decompiler_version: "foobar")
smart_contract = insert(:decompiled_smart_contract, decompiler_version: "bizbuz")
response =
conn
|> get("/api", Map.merge(params, %{"filter" => "decompiled", "not_decompiled_with_version" => "foobar"}))
|> json_response(200)
assert response["message"] == "OK"
assert response["status"] == "1"
assert response["result"] == [
%{
"ABI" => "Contract source code not verified",
"Address" => to_string(smart_contract.address_hash),
"CompilerVersion" => "",
"ContractName" => "",
"DecompiledSourceCode" => smart_contract.decompiled_source_code,
"DecompilerVersion" => "bizbuz",
"OptimizationUsed" => "",
"SourceCode" => ""
}
]
end
test "filtering for only decompiled contracts, with a decompiled with version filter, where another decompiled version exists",
%{params: params, conn: conn} do
non_match = insert(:decompiled_smart_contract, decompiler_version: "foobar")
insert(:decompiled_smart_contract, decompiler_version: "bizbuz", address_hash: non_match.address_hash)
smart_contract = insert(:decompiled_smart_contract, decompiler_version: "bizbuz")
response =
conn
|> get("/api", Map.merge(params, %{"filter" => "decompiled", "not_decompiled_with_version" => "foobar"}))
|> json_response(200)
assert response["message"] == "OK"
assert response["status"] == "1"
assert response["result"] == [
%{
"ABI" => "Contract source code not verified",
"Address" => to_string(smart_contract.address_hash),
"CompilerVersion" => "",
"ContractName" => "",
"DecompiledSourceCode" => smart_contract.decompiled_source_code,
"DecompilerVersion" => "bizbuz",
"OptimizationUsed" => "",
"SourceCode" => ""
}
]
end
test "filtering for only not_decompiled (and by extension not verified contracts)", %{params: params, conn: conn} do test "filtering for only not_decompiled (and by extension not verified contracts)", %{params: params, conn: conn} do
insert(:decompiled_smart_contract) insert(:decompiled_smart_contract)
insert(:smart_contract) insert(:smart_contract)
@ -359,4 +414,100 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
assert response["message"] == "OK" assert response["message"] == "OK"
end end
end end
describe "verify" do
test "with an address that doesn't exist", %{conn: conn} do
contract_code_info = Factory.contract_code_info()
contract_address = insert(:contract_address, contract_code: contract_code_info.bytecode)
params = %{
"module" => "contract",
"action" => "verify",
"addressHash" => to_string(contract_address.hash),
"name" => contract_code_info.name,
"compilerVersion" => contract_code_info.version,
"optimization" => contract_code_info.optimized,
"contractSourceCode" => contract_code_info.source_code
}
response =
conn
|> get("/api", params)
|> json_response(200)
expected_result = %{
"Address" => to_string(contract_address.hash),
"SourceCode" => contract_code_info.source_code,
"ABI" => Jason.encode!(contract_code_info.abi),
"ContractName" => contract_code_info.name,
"CompilerVersion" => contract_code_info.version,
"DecompiledSourceCode" => "Contract source code not decompiled.",
"DecompilerVersion" => "",
"OptimizationUsed" => "0"
}
assert response["status"] == "1"
assert response["result"] == expected_result
assert response["message"] == "OK"
end
test "with external libraries", %{conn: conn} do
contract_data =
"#{File.cwd!()}/test/support/fixture/smart_contract/compiler_tests.json"
|> File.read!()
|> Jason.decode!()
|> List.first()
%{
"compiler_version" => compiler_version,
"external_libraries" => external_libraries,
"name" => name,
"optimize" => optimize,
"contract" => contract_source_code,
"expected_bytecode" => expected_bytecode
} = contract_data
contract_address = insert(:contract_address, contract_code: "0x" <> expected_bytecode)
params = %{
"module" => "contract",
"action" => "verify",
"addressHash" => to_string(contract_address.hash),
"name" => name,
"compilerVersion" => compiler_version,
"optimization" => optimize,
"contractSourceCode" => contract_source_code
}
params_with_external_libraries =
external_libraries
|> Enum.with_index()
|> Enum.reduce(params, fn {{name, address}, index}, acc ->
name_key = "library#{index + 1}Name"
address_key = "library#{index + 1}Address"
acc
|> Map.put(name_key, name)
|> Map.put(address_key, address)
end)
response =
conn
|> get("/api", params_with_external_libraries)
|> json_response(200)
assert response["status"] == "1"
assert response["message"] == "OK"
result = response["result"]
assert result["Address"] == to_string(contract_address.hash)
assert result["SourceCode"] == contract_source_code
assert result["ContractName"] == name
assert result["DecompiledSourceCode"] == "Contract source code not decompiled."
assert result["DecompilerVersion"] == ""
assert result["OptimizationUsed"] == "1"
end
end
end end

File diff suppressed because one or more lines are too long

@ -46,6 +46,7 @@ defmodule EthereumJSONRPC.Contract do
|> case do |> case do
{:ok, responses} -> responses {:ok, responses} -> responses
{:error, {:bad_gateway, _request_url}} -> raise "Bad gateway" {:error, {:bad_gateway, _request_url}} -> raise "Bad gateway"
{:error, reason} when is_atom(reason) -> raise Atom.to_string(reason)
{:error, error} -> raise error {:error, error} -> raise error
end end
|> Enum.into(%{}, &{&1.id, &1}) |> Enum.into(%{}, &{&1.id, &1})

@ -20,6 +20,7 @@ defmodule Explorer.Chain do
import EthereumJSONRPC, only: [integer_to_quantity: 1] import EthereumJSONRPC, only: [integer_to_quantity: 1]
alias Ecto.Adapters.SQL
alias Ecto.{Changeset, Multi} alias Ecto.{Changeset, Multi}
alias Explorer.Chain.{ alias Explorer.Chain.{
@ -1898,7 +1899,16 @@ defmodule Explorer.Chain do
""" """
@spec transaction_estimated_count() :: non_neg_integer() @spec transaction_estimated_count() :: non_neg_integer()
def transaction_estimated_count do def transaction_estimated_count do
TransactionCountCache.value() cached_value = TransactionCountCache.value()
if is_nil(cached_value) do
%Postgrex.Result{rows: [[rows]]} =
SQL.query!(Repo, "SELECT reltuples::BIGINT AS estimate FROM pg_class WHERE relname='transactions'")
rows
else
cached_value
end
end end
@doc """ @doc """
@ -2638,37 +2648,38 @@ defmodule Explorer.Chain do
@spec data() :: Dataloader.Ecto.t() @spec data() :: Dataloader.Ecto.t()
def data, do: DataloaderEcto.new(Repo) def data, do: DataloaderEcto.new(Repo)
@doc """ def list_decompiled_contracts(limit, offset, not_decompiled_with_version \\ nil) do
Returns a list of block numbers with invalid consensus.
"""
@spec list_block_numbers_with_invalid_consensus :: [integer()]
def list_block_numbers_with_invalid_consensus do
query =
from(
block in Block,
join: parent in Block,
on: parent.hash == block.parent_hash,
where: block.consensus == true,
where: parent.consensus == false,
select: parent.number
)
Repo.all(query, timeout: :infinity)
end
def list_decompiled_contracts(limit, offset) do
query = query =
from( from(
address in Address, address in Address,
join: decompiled_smart_contract in DecompiledSmartContract, where:
on: decompiled_smart_contract.address_hash == address.hash, fragment(
preload: [{:decompiled_smart_contract, decompiled_smart_contract}, :smart_contract], "EXISTS (SELECT 1 FROM decompiled_smart_contracts WHERE decompiled_smart_contracts.address_hash = ?)",
address.hash
),
preload: [:decompiled_smart_contracts, :smart_contract],
order_by: [asc: address.inserted_at], order_by: [asc: address.inserted_at],
limit: ^limit, limit: ^limit,
offset: ^offset offset: ^offset
) )
Repo.all(query) query
|> filter_decompiled_with_version(not_decompiled_with_version)
|> Repo.all()
end
defp filter_decompiled_with_version(query, nil) do
query
end
defp filter_decompiled_with_version(query, not_decompiled_with_version) do
from(address in query,
left_join: decompiled_smart_contract in DecompiledSmartContract,
on: decompiled_smart_contract.decompiler_version == ^not_decompiled_with_version,
on: decompiled_smart_contract.address_hash == address.hash,
where: is_nil(decompiled_smart_contract.id),
distinct: [address.hash]
)
end end
def list_verified_contracts(limit, offset) do def list_verified_contracts(limit, offset) do
@ -2678,7 +2689,7 @@ defmodule Explorer.Chain do
where: not is_nil(address.contract_code), where: not is_nil(address.contract_code),
join: smart_contract in SmartContract, join: smart_contract in SmartContract,
on: smart_contract.address_hash == address.hash, on: smart_contract.address_hash == address.hash,
preload: [{:smart_contract, smart_contract}, :decompiled_smart_contract], preload: [{:smart_contract, smart_contract}, :decompiled_smart_contracts],
order_by: [asc: address.inserted_at], order_by: [asc: address.inserted_at],
limit: ^limit, limit: ^limit,
offset: ^offset offset: ^offset
@ -2692,7 +2703,7 @@ defmodule Explorer.Chain do
from( from(
address in Address, address in Address,
where: not is_nil(address.contract_code), where: not is_nil(address.contract_code),
preload: [:smart_contract, :decompiled_smart_contract], preload: [:smart_contract, :decompiled_smart_contracts],
order_by: [asc: address.inserted_at], order_by: [asc: address.inserted_at],
limit: ^limit, limit: ^limit,
offset: ^offset offset: ^offset
@ -2709,7 +2720,7 @@ defmodule Explorer.Chain do
on: smart_contract.address_hash == address.hash, on: smart_contract.address_hash == address.hash,
where: not is_nil(address.contract_code), where: not is_nil(address.contract_code),
where: is_nil(smart_contract.address_hash), where: is_nil(smart_contract.address_hash),
preload: [{:smart_contract, smart_contract}, :decompiled_smart_contract], preload: [{:smart_contract, smart_contract}, :decompiled_smart_contracts],
order_by: [asc: address.inserted_at], order_by: [asc: address.inserted_at],
limit: ^limit, limit: ^limit,
offset: ^offset offset: ^offset
@ -2722,11 +2733,16 @@ defmodule Explorer.Chain do
query = query =
from( from(
address in Address, address in Address,
where:
fragment(
"NOT EXISTS (SELECT 1 FROM decompiled_smart_contracts WHERE decompiled_smart_contracts.address_hash = ?)",
address.hash
),
left_join: smart_contract in SmartContract, left_join: smart_contract in SmartContract,
on: smart_contract.address_hash == address.hash, on: smart_contract.address_hash == address.hash,
left_join: decompiled_smart_contract in DecompiledSmartContract, left_join: decompiled_smart_contract in DecompiledSmartContract,
on: decompiled_smart_contract.address_hash == address.hash, on: decompiled_smart_contract.address_hash == address.hash,
preload: [smart_contract: smart_contract, decompiled_smart_contract: decompiled_smart_contract], preload: [:smart_contract, :decompiled_smart_contracts],
where: not is_nil(address.contract_code), where: not is_nil(address.contract_code),
where: is_nil(smart_contract.address_hash), where: is_nil(smart_contract.address_hash),
where: is_nil(decompiled_smart_contract.address_hash), where: is_nil(decompiled_smart_contract.address_hash),

@ -62,6 +62,7 @@ defmodule Explorer.Chain.Address do
except: [ except: [
:__meta__, :__meta__,
:smart_contract, :smart_contract,
:decompiled_smart_contract,
:token, :token,
:contracts_creation_internal_transaction, :contracts_creation_internal_transaction,
:contracts_creation_transaction, :contracts_creation_transaction,
@ -77,7 +78,6 @@ defmodule Explorer.Chain.Address do
field(:has_decompiled_code?, :boolean, virtual: true) field(:has_decompiled_code?, :boolean, virtual: true)
has_one(:smart_contract, SmartContract) has_one(:smart_contract, SmartContract)
has_one(:decompiled_smart_contract, DecompiledSmartContract)
has_one(:token, Token, foreign_key: :contract_address_hash) has_one(:token, Token, foreign_key: :contract_address_hash)
has_one( has_one(

@ -46,7 +46,7 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
|> Map.put(:timestamps, timestamps) |> Map.put(:timestamps, timestamps)
ordered_consensus_block_numbers = ordered_consensus_block_numbers(changes_list) ordered_consensus_block_numbers = ordered_consensus_block_numbers(changes_list)
where_invalid_parent = where_invalid_parent(changes_list) where_invalid_neighbour = where_invalid_neighbour(changes_list)
where_forked = where_forked(changes_list) where_forked = where_forked(changes_list)
multi multi
@ -70,8 +70,8 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
|> Multi.run(:lose_consensus, fn repo, _ -> |> Multi.run(:lose_consensus, fn repo, _ ->
lose_consensus(repo, ordered_consensus_block_numbers, insert_options) lose_consensus(repo, ordered_consensus_block_numbers, insert_options)
end) end)
|> Multi.run(:lose_invalid_parent_consensus, fn repo, _ -> |> Multi.run(:lose_invalid_neighbour_consensus, fn repo, _ ->
lose_invalid_parent_consensus(repo, where_invalid_parent, insert_options) lose_invalid_neighbour_consensus(repo, where_invalid_neighbour, insert_options)
end) end)
|> Multi.run(:delete_address_token_balances, fn repo, _ -> |> Multi.run(:delete_address_token_balances, fn repo, _ ->
delete_address_token_balances(repo, ordered_consensus_block_numbers, insert_options) delete_address_token_balances(repo, ordered_consensus_block_numbers, insert_options)
@ -316,13 +316,13 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
end end
end end
defp lose_invalid_parent_consensus(repo, where_invalid_parent, %{ defp lose_invalid_neighbour_consensus(repo, where_invalid_neighbour, %{
timeout: timeout, timeout: timeout,
timestamps: %{updated_at: updated_at} timestamps: %{updated_at: updated_at}
}) do }) do
query = query =
from( from(
block in where_invalid_parent, block in where_invalid_neighbour,
update: [ update: [
set: [ set: [
consensus: false, consensus: false,
@ -338,7 +338,7 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
{:ok, result} {:ok, result}
rescue rescue
postgrex_error in Postgrex.Error -> postgrex_error in Postgrex.Error ->
{:error, %{exception: postgrex_error, where_invalid_parent: where_invalid_parent}} {:error, %{exception: postgrex_error, where_invalid_neighbour: where_invalid_neighbour}}
end end
end end
@ -581,12 +581,22 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
end) end)
end end
defp where_invalid_parent(blocks_changes) when is_list(blocks_changes) do defp where_invalid_neighbour(blocks_changes) when is_list(blocks_changes) do
initial = from(b in Block, where: false) initial = from(b in Block, where: false)
Enum.reduce(blocks_changes, initial, fn %{consensus: consensus, parent_hash: parent_hash, number: number}, acc -> Enum.reduce(blocks_changes, initial, fn %{
consensus: consensus,
hash: hash,
parent_hash: parent_hash,
number: number
},
acc ->
if consensus do if consensus do
from(block in acc, or_where: block.number == ^(number - 1) and block.hash != ^parent_hash) from(
block in acc,
or_where: block.number == ^(number - 1) and block.hash != ^parent_hash,
or_where: block.number == ^(number + 1) and block.parent_hash != ^hash
)
else else
acc acc
end end

@ -209,8 +209,8 @@ defmodule Explorer.Chain.SmartContract do
field(:constructor_arguments, :string) field(:constructor_arguments, :string)
field(:abi, {:array, :map}) field(:abi, {:array, :map})
has_one( has_many(
:decompiled_smart_contract, :decompiled_smart_contracts,
DecompiledSmartContract, DecompiledSmartContract,
foreign_key: :address_hash foreign_key: :address_hash
) )
@ -227,7 +227,7 @@ defmodule Explorer.Chain.SmartContract do
end end
def preload_decompiled_smart_contract(contract) do def preload_decompiled_smart_contract(contract) do
Repo.preload(contract, :decompiled_smart_contract) Repo.preload(contract, :decompiled_smart_contracts)
end end
def changeset(%__MODULE__{} = smart_contract, attrs) do def changeset(%__MODULE__{} = smart_contract, attrs) do

@ -12,7 +12,7 @@ defmodule Explorer.Chain.TransactionCountCache do
# 2 hours # 2 hours
@cache_period 1_000 * 60 * 60 * 2 @cache_period 1_000 * 60 * 60 * 2
@default_value 0 @default_value nil
@key "count" @key "count"
@name __MODULE__ @name __MODULE__

@ -13,10 +13,10 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do
## Examples ## Examples
iex(1)> Explorer.SmartContract.Solidity.CodeCompiler.run( iex(1)> Explorer.SmartContract.Solidity.CodeCompiler.run([
...> "SimpleStorage", ...> name: "SimpleStorage",
...> "v0.4.24+commit.e67f0147", ...> compiler_version: "v0.4.24+commit.e67f0147",
...> \""" ...> code: \"""
...> pragma solidity ^0.4.24; ...> pragma solidity ^0.4.24;
...> ...>
...> contract SimpleStorage { ...> contract SimpleStorage {
@ -31,8 +31,8 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do
...> } ...> }
...> } ...> }
...> \""", ...> \""",
...> false ...> optimize: false, evm_version: "byzantium"
...> ) ...> ])
{ {
:ok, :ok,
%{ %{
@ -61,10 +61,18 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do
} }
} }
""" """
def run(name, compiler_version, code, optimize, evm_version \\ "byzantium", external_libs \\ %{}) do def run(params) do
name = Keyword.fetch!(params, :name)
compiler_version = Keyword.fetch!(params, :compiler_version)
code = Keyword.fetch!(params, :code)
optimize = Keyword.fetch!(params, :optimize)
optimization_runs = params |> Keyword.get(:optimization_runs, 200) |> Integer.to_string()
evm_version = Keyword.get(params, :evm_version, List.last(@allowed_evm_versions))
external_libs = Keyword.get(params, :external_libs, %{})
external_libs_string = Jason.encode!(external_libs) external_libs_string = Jason.encode!(external_libs)
evm_version = checked_evm_version =
if evm_version in @allowed_evm_versions do if evm_version in @allowed_evm_versions do
evm_version evm_version
else else
@ -79,9 +87,10 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do
code, code,
compiler_version, compiler_version,
optimize_value(optimize), optimize_value(optimize),
optimization_runs,
@new_contract_name, @new_contract_name,
external_libs_string, external_libs_string,
evm_version checked_evm_version
] ]
) )

@ -24,9 +24,18 @@ defmodule Explorer.SmartContract.Verifier do
external_libraries = Map.get(params, "external_libraries", %{}) external_libraries = Map.get(params, "external_libraries", %{})
constructor_arguments = Map.get(params, "constructor_arguments", "") constructor_arguments = Map.get(params, "constructor_arguments", "")
evm_version = Map.get(params, "evm_version", "byzantium") evm_version = Map.get(params, "evm_version", "byzantium")
optimization_runs = Map.get(params, "optimization_runs", 200)
solc_output = solc_output =
CodeCompiler.run(name, compiler_version, contract_source_code, optimization, evm_version, external_libraries) CodeCompiler.run(
name: name,
compiler_version: compiler_version,
code: contract_source_code,
optimize: optimization,
optimization_runs: optimization_runs,
evm_version: evm_version,
external_libs: external_libraries
)
case compare_bytecodes(solc_output, address_hash, constructor_arguments) do case compare_bytecodes(solc_output, address_hash, constructor_arguments) do
{:error, :generated_bytecode} -> {:error, :generated_bytecode} ->
@ -34,12 +43,12 @@ defmodule Explorer.SmartContract.Verifier do
second_solc_output = second_solc_output =
CodeCompiler.run( CodeCompiler.run(
name, name: name,
compiler_version, compiler_version: compiler_version,
contract_source_code, code: contract_source_code,
optimization, optimize: optimization,
next_evm_version, evm_version: next_evm_version,
external_libraries external_libs: external_libraries
) )
compare_bytecodes(second_solc_output, address_hash, constructor_arguments) compare_bytecodes(second_solc_output, address_hash, constructor_arguments)

@ -5,9 +5,10 @@ const solc = require('solc');
var sourceCode = process.argv[2]; var sourceCode = process.argv[2];
var version = process.argv[3]; var version = process.argv[3];
var optimize = process.argv[4]; var optimize = process.argv[4];
var newContractName = process.argv[5]; var optimizationRuns = parseInt(process.argv[5], 10);
var externalLibraries = JSON.parse(process.argv[6]) var newContractName = process.argv[6];
var evmVersion = process.argv[7] var externalLibraries = JSON.parse(process.argv[7])
var evmVersion = process.argv[8];
var compiled_code = solc.loadRemoteVersion(version, function (err, solcSnapshot) { var compiled_code = solc.loadRemoteVersion(version, function (err, solcSnapshot) {
if (err) { if (err) {
@ -24,7 +25,7 @@ var compiled_code = solc.loadRemoteVersion(version, function (err, solcSnapshot)
evmVersion: evmVersion, evmVersion: evmVersion,
optimizer: { optimizer: {
enabled: optimize == '1', enabled: optimize == '1',
runs: 200 runs: optimizationRuns
}, },
libraries: { libraries: {
[newContractName]: externalLibraries [newContractName]: externalLibraries

@ -261,34 +261,36 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do
end end
# Regression test for https://github.com/poanetwork/blockscout/issues/1644 # Regression test for https://github.com/poanetwork/blockscout/issues/1644
test "discards parent block if it isn't related to the current one because of reorg", test "discards neighbouring blocks if they aren't related to the current one because of reorg and/or import timeout",
%{consensus_block: %Block{number: block_number, hash: block_hash, miner_hash: miner_hash}, options: options} do %{consensus_block: %Block{number: block_number, hash: block_hash, miner_hash: miner_hash}, options: options} do
old_block = insert(:block, parent_hash: block_hash, number: block_number + 1) old_block1 = params_for(:block, miner_hash: miner_hash, parent_hash: block_hash, number: block_number + 1)
insert(:block, parent_hash: old_block.hash, number: old_block.number + 1)
new_block1 = params_for(:block, parent_hash: block_hash, number: block_number + 1, miner_hash: miner_hash) new_block1 = params_for(:block, miner_hash: miner_hash, parent_hash: block_hash, number: block_number + 1)
new_block2 = params_for(:block, miner_hash: miner_hash, parent_hash: new_block1.hash, number: block_number + 2)
new_block2 = range = block_number..(block_number + 2)
params_for(:block, parent_hash: new_block1.hash, number: new_block1.number + 1, miner_hash: miner_hash)
%Ecto.Changeset{valid?: true, changes: block_changes} = Block.changeset(%Block{}, new_block2) insert_block(new_block1, options)
changes_list = [block_changes] insert_block(new_block2, options)
assert Chain.missing_block_number_ranges(range) == []
Multi.new() insert_block(old_block1, options)
|> Blocks.run(changes_list, options) assert Chain.missing_block_number_ranges(range) == [(block_number + 2)..(block_number + 2)]
|> Repo.transaction()
assert Chain.missing_block_number_ranges(block_number..new_block2.number) == [old_block.number..old_block.number] insert_block(new_block2, options)
assert Chain.missing_block_number_ranges(range) == [(block_number + 1)..(block_number + 1)]
%Ecto.Changeset{valid?: true, changes: block_changes} = Block.changeset(%Block{}, new_block1) insert_block(new_block1, options)
changes_list = [block_changes] assert Chain.missing_block_number_ranges(range) == []
end
end
defp insert_block(block_params, options) do
%Ecto.Changeset{valid?: true, changes: block_changes} = Block.changeset(%Block{}, block_params)
Multi.new() Multi.new()
|> Blocks.run(changes_list, options) |> Blocks.run([block_changes], options)
|> Repo.transaction() |> Repo.transaction()
assert Chain.missing_block_number_ranges(block_number..new_block2.number) == []
end
end end
defp count(schema) do defp count(schema) do

@ -8,7 +8,7 @@ defmodule Explorer.Chain.TransactionCountCacheTest do
result = TransactionCountCache.value(TestCache) result = TransactionCountCache.value(TestCache)
assert result == 0 assert is_nil(result)
end end
test "updates cache if initial value is zero" do test "updates cache if initial value is zero" do

@ -3667,27 +3667,6 @@ defmodule Explorer.ChainTest do
end end
end end
describe "list_block_numbers_with_invalid_consensus/0" do
test "returns a list of block numbers with invalid consensus" do
block1 = insert(:block)
block2_with_invalid_consensus = insert(:block, parent_hash: block1.hash, consensus: false)
_block2 = insert(:block, parent_hash: block1.hash, number: block2_with_invalid_consensus.number)
block3 = insert(:block, parent_hash: block2_with_invalid_consensus.hash)
block4 = insert(:block, parent_hash: block3.hash)
block5 = insert(:block, parent_hash: block4.hash)
block6_without_consensus = insert(:block, parent_hash: block5.hash, consensus: false)
block6 = insert(:block, parent_hash: block5.hash, number: block6_without_consensus.number)
block7 = insert(:block, parent_hash: block6.hash)
block8_with_invalid_consensus = insert(:block, parent_hash: block7.hash, consensus: false)
_block8 = insert(:block, parent_hash: block7.hash, number: block8_with_invalid_consensus.number)
block9 = insert(:block, parent_hash: block8_with_invalid_consensus.hash)
_block10 = insert(:block, parent_hash: block9.hash)
assert Chain.list_block_numbers_with_invalid_consensus() ==
[block2_with_invalid_consensus.number, block8_with_invalid_consensus.number]
end
end
describe "block_combined_rewards/1" do describe "block_combined_rewards/1" do
test "sums the block_rewards values" do test "sums the block_rewards values" do
block = insert(:block) block = insert(:block)

@ -18,10 +18,11 @@ defmodule Explorer.SmartContract.Solidity.CodeCompilerTest do
test "compiles the latest solidity version", %{contract_code_info: contract_code_info} do test "compiles the latest solidity version", %{contract_code_info: contract_code_info} do
response = response =
CodeCompiler.run( CodeCompiler.run(
contract_code_info.name, name: contract_code_info.name,
contract_code_info.version, compiler_version: contract_code_info.version,
contract_code_info.source_code, code: contract_code_info.source_code,
contract_code_info.optimized optimize: contract_code_info.optimized,
evm_version: "byzantium"
) )
assert {:ok, assert {:ok,
@ -37,10 +38,11 @@ defmodule Explorer.SmartContract.Solidity.CodeCompilerTest do
response = response =
CodeCompiler.run( CodeCompiler.run(
contract_code_info.name, name: contract_code_info.name,
contract_code_info.version, compiler_version: contract_code_info.version,
contract_code_info.source_code, code: contract_code_info.source_code,
optimize optimize: optimize,
evm_version: "byzantium"
) )
assert {:ok, assert {:ok,
@ -61,12 +63,12 @@ defmodule Explorer.SmartContract.Solidity.CodeCompilerTest do
{:ok, result} = {:ok, result} =
CodeCompiler.run( CodeCompiler.run(
name, name: name,
compiler_version, compiler_version: compiler_version,
contract, code: contract,
optimize, optimize: optimize,
"byzantium", evm_version: "byzantium",
external_libraries external_libs: external_libraries
) )
clean_result = remove_init_data_and_whisper_data(result["bytecode"]) clean_result = remove_init_data_and_whisper_data(result["bytecode"])
@ -109,7 +111,14 @@ defmodule Explorer.SmartContract.Solidity.CodeCompilerTest do
evm_version = "constantinople" evm_version = "constantinople"
response = CodeCompiler.run(name, version, code, optimize, evm_version) response =
CodeCompiler.run(
name: name,
compiler_version: version,
code: code,
optimize: optimize,
evm_version: evm_version
)
assert {:ok, assert {:ok,
%{ %{
@ -139,7 +148,7 @@ defmodule Explorer.SmartContract.Solidity.CodeCompilerTest do
version = "v0.1.3-nightly.2015.9.25+commit.4457170" version = "v0.1.3-nightly.2015.9.25+commit.4457170"
response = CodeCompiler.run(name, version, code, optimize) response = CodeCompiler.run(name: name, compiler_version: version, code: code, optimize: optimize)
assert {:ok, assert {:ok,
%{ %{
@ -156,10 +165,10 @@ defmodule Explorer.SmartContract.Solidity.CodeCompilerTest do
response = response =
CodeCompiler.run( CodeCompiler.run(
contract_code_info.name, name: contract_code_info.name,
contract_code_info.version, compiler_version: contract_code_info.version,
wrong_code, code: wrong_code,
contract_code_info.optimized optimize: contract_code_info.optimized
) )
assert {:error, :compilation} = response assert {:error, :compilation} = response

@ -31,7 +31,7 @@ block_transformer =
config :indexer, config :indexer,
block_transformer: block_transformer, block_transformer: block_transformer,
ecto_repos: [Explorer.Repo], ecto_repos: [Explorer.Repo],
metadata_updater_days_interval: 7, metadata_updater_days_interval: 2,
# bytes # bytes
memory_limit: 1 <<< 30, memory_limit: 1 <<< 30,
first_block: System.get_env("FIRST_BLOCK") || 0 first_block: System.get_env("FIRST_BLOCK") || 0

@ -1,45 +0,0 @@
defmodule Indexer.Block.InvalidConsensus.Supervisor do
@moduledoc """
Supervises process for ensuring blocks with invalid consensus get queued for
indexing.
"""
use Supervisor
alias Indexer.Block.InvalidConsensus.Worker
def child_spec([]) do
child_spec([[]])
end
def child_spec([init_arguments]) do
child_spec([init_arguments, [name: __MODULE__]])
end
def child_spec([_init_arguments, _gen_server_options] = start_link_arguments) do
spec = %{
id: __MODULE__,
start: {__MODULE__, :start_link, start_link_arguments},
restart: :transient,
type: :supervisor
}
Supervisor.child_spec(spec, [])
end
def start_link(init_arguments, gen_server_options \\ []) do
Supervisor.start_link(__MODULE__, init_arguments, gen_server_options)
end
@impl Supervisor
def init(_) do
children = [
{Worker, [[supervisor: self()], [name: Worker]]},
{Task.Supervisor, name: Indexer.Block.InvalidConsensus.TaskSupervisor}
]
opts = [strategy: :one_for_all]
Supervisor.init(children, opts)
end
end

@ -1,99 +0,0 @@
defmodule Indexer.Block.InvalidConsensus.Worker do
@moduledoc """
Finds blocks with invalid consensus and queues them up to be refetched. This
process does this once, after the application starts up.
A block has invalid consensus when it is referenced as the parent hash of a
block with consensus while not having consensus (consensus=false). Only one
block can have consensus at a given height (block number).
"""
use GenServer
require Logger
alias Explorer.Chain
alias Indexer.Block.Catchup.Fetcher
alias Indexer.Block.InvalidConsensus.TaskSupervisor
def child_spec([init_arguments]) do
child_spec([init_arguments, []])
end
def child_spec([_init_arguments, _gen_server_options] = start_link_arguments) do
spec = %{
id: __MODULE__,
start: {__MODULE__, :start_link, start_link_arguments},
restart: :transient,
type: :worker
}
Supervisor.child_spec(spec, [])
end
def start_link(init_arguments, gen_server_options \\ []) do
GenServer.start_link(__MODULE__, init_arguments, gen_server_options)
end
def init(opts) do
sup_pid = Keyword.fetch!(opts, :supervisor)
retry_interval = Keyword.get(opts, :retry_interval, 10_000)
send(self(), :scan)
state = %{
block_numbers: [],
retry_interval: retry_interval,
sup_pid: sup_pid,
task_ref: nil
}
{:ok, state}
end
def handle_info(:scan, state) do
block_numbers = Chain.list_block_numbers_with_invalid_consensus()
case block_numbers do
[] ->
Supervisor.stop(state.sup_pid, :normal)
{:noreply, state}
block_numbers ->
Process.send_after(self(), :push_front_blocks, state.retry_interval)
{:noreply, %{state | block_numbers: block_numbers}}
end
end
def handle_info(:push_front_blocks, %{block_numbers: block_numbers} = state) do
%Task{ref: ref} = async_push_front(block_numbers)
{:noreply, %{state | task_ref: ref}}
end
def handle_info({ref, :ok}, %{task_ref: ref, sup_pid: sup_pid}) do
Process.demonitor(ref, [:flush])
Supervisor.stop(sup_pid, :normal)
{:stop, :shutdown}
end
def handle_info({ref, {:error, reason}}, %{task_ref: ref, retry_interval: millis} = state) do
case reason do
:queue_unavailable -> :ok
_ -> Logger.error(fn -> inspect(reason) end)
end
Process.demonitor(ref, [:flush])
Process.send_after(self(), :push_front_blocks, millis)
{:noreply, %{state | task_ref: nil}}
end
def handle_info({:DOWN, ref, :process, _, _}, %{task_ref: ref, retry_interval: millis} = state) do
Process.send_after(self(), :push_front_blocks, millis)
{:noreply, %{state | task_ref: nil}}
end
defp async_push_front(block_numbers) do
Task.Supervisor.async_nolink(TaskSupervisor, Fetcher, :push_front, [block_numbers])
end
end

@ -1,34 +0,0 @@
defmodule Indexer.Block.Realtime.ConsensusEnsurer do
@moduledoc """
Triggers a refetch if a given block doesn't have consensus.
"""
require Logger
alias Explorer.Chain
alias Explorer.Chain.Hash
alias Indexer.Block.Realtime.Fetcher
def perform(_, number, _) when not is_integer(number) or number < 0, do: :ok
def perform(%Hash{byte_count: unquote(Hash.Full.byte_count())} = block_hash, number, block_fetcher) do
case Chain.hash_to_block(block_hash) do
{:ok, %{consensus: true} = _block} ->
:ignore
_ ->
Logger.info(fn ->
[
"refetch from consensus was found on block (",
to_string(number),
"). A reorg initiated."
]
end)
# trigger refetch if consensus=false or block was not found
Fetcher.fetch_and_import_block(number, block_fetcher, true)
end
:ok
end
end

@ -27,7 +27,7 @@ defmodule Indexer.Block.Realtime.Fetcher do
alias Explorer.Chain.TokenTransfer alias Explorer.Chain.TokenTransfer
alias Explorer.Counters.AverageBlockTime alias Explorer.Counters.AverageBlockTime
alias Indexer.{AddressExtraction, Block, TokenBalances, Tracer} alias Indexer.{AddressExtraction, Block, TokenBalances, Tracer}
alias Indexer.Block.Realtime.{ConsensusEnsurer, TaskSupervisor} alias Indexer.Block.Realtime.TaskSupervisor
alias Timex.Duration alias Timex.Duration
@behaviour Block.Fetcher @behaviour Block.Fetcher
@ -269,12 +269,7 @@ defmodule Indexer.Block.Realtime.Fetcher do
@decorate span(tracer: Tracer) @decorate span(tracer: Tracer)
defp do_fetch_and_import_block(block_number_to_fetch, block_fetcher, retry) do defp do_fetch_and_import_block(block_number_to_fetch, block_fetcher, retry) do
case fetch_and_import_range(block_fetcher, block_number_to_fetch..block_number_to_fetch) do case fetch_and_import_range(block_fetcher, block_number_to_fetch..block_number_to_fetch) do
{:ok, %{inserted: inserted, errors: []}} -> {:ok, %{inserted: _, errors: []}} ->
for block <- Map.get(inserted, :blocks, []) do
args = [block.parent_hash, block.number - 1, block_fetcher]
Task.Supervisor.start_child(TaskSupervisor, ConsensusEnsurer, :perform, args)
end
Logger.debug("Fetched and imported.") Logger.debug("Fetched and imported.")
{:ok, %{inserted: _, errors: [_ | _] = errors}} -> {:ok, %{inserted: _, errors: [_ | _] = errors}} ->

@ -4,7 +4,7 @@ defmodule Indexer.Block.Supervisor do
""" """
alias Indexer.Block alias Indexer.Block
alias Indexer.Block.{Catchup, InvalidConsensus, Realtime, Reward, Uncle} alias Indexer.Block.{Catchup, Realtime, Reward, Uncle}
alias Indexer.Temporary.{AddressesWithoutCode, FailedCreatedAddresses} alias Indexer.Temporary.{AddressesWithoutCode, FailedCreatedAddresses}
use Supervisor use Supervisor
@ -50,7 +50,6 @@ defmodule Indexer.Block.Supervisor do
%{block_fetcher: block_fetcher, block_interval: block_interval, memory_monitor: memory_monitor}, %{block_fetcher: block_fetcher, block_interval: block_interval, memory_monitor: memory_monitor},
[name: Catchup.Supervisor] [name: Catchup.Supervisor]
]}, ]},
{InvalidConsensus.Supervisor, [[], [name: InvalidConsensus.Supervisor]]},
{Realtime.Supervisor, {Realtime.Supervisor,
[ [
%{block_fetcher: realtime_block_fetcher, subscribe_named_arguments: realtime_subscribe_named_arguments}, %{block_fetcher: realtime_block_fetcher, subscribe_named_arguments: realtime_subscribe_named_arguments},

@ -52,11 +52,8 @@ defmodule Indexer.Token.Fetcher do
@decorate trace(name: "fetch", resource: "Indexer.Token.Fetcher.run/2", service: :indexer, tracer: Tracer) @decorate trace(name: "fetch", resource: "Indexer.Token.Fetcher.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) do case Chain.token_from_address_hash(token_contract_address) do
{:ok, %Token{cataloged: false} = token} -> {:ok, %Token{} = token} ->
catalog_token(token) catalog_token(token)
{:ok, _} ->
:ok
end end
end end
@ -74,7 +71,7 @@ defmodule Indexer.Token.Fetcher do
|> MetadataRetriever.get_functions_of() |> MetadataRetriever.get_functions_of()
|> Map.put(:cataloged, true) |> Map.put(:cataloged, true)
{:ok, _} = Chain.update_token(token, token_params) {:ok, _} = Chain.update_token(%{token | updated_at: DateTime.utc_now()}, token_params)
:ok :ok
end end
end end

@ -1,87 +0,0 @@
defmodule Indexer.Block.InvalidConsensus.WorkerTest do
use Explorer.DataCase
alias Indexer.Sequence
alias Indexer.Block.InvalidConsensus.{Worker, TaskSupervisor}
@moduletag :capture_log
describe "start_link/1" do
test "starts the worker" do
assert {:ok, _pid} = Worker.start_link(supervisor: self())
end
end
describe "init/1" do
test "sends message to self" do
pid = self()
assert {:ok, %{task_ref: nil, block_numbers: [], sup_pid: ^pid}} = Worker.init(supervisor: self())
assert_received :scan
end
end
describe "handle_info with :scan" do
test "sends shutdown to supervisor" do
state = %{task_ref: nil, block_numbers: [], sup_pid: self()}
Task.async(fn -> Worker.handle_info(:scan, state) end)
assert_receive {_, _, {:terminate, :normal}}
end
test "sends message to self when blocks with invalid consensus are found" do
block1 = insert(:block)
block2_with_invalid_consensus = insert(:block, parent_hash: block1.hash, consensus: false)
_block2 = insert(:block, parent_hash: block1.hash, number: block2_with_invalid_consensus.number)
_block3 = insert(:block, parent_hash: block2_with_invalid_consensus.hash)
block_number = block2_with_invalid_consensus.number
expected_state = %{task_ref: nil, block_numbers: [block_number], retry_interval: 1}
state = %{task_ref: nil, block_numbers: [], retry_interval: 1}
assert {:noreply, ^expected_state} = Worker.handle_info(:scan, state)
assert_receive :push_front_blocks
end
end
describe "handle_info with :push_front_blocks" do
test "starts a task" do
task_sup_pid = start_supervised!({Task.Supervisor, name: TaskSupervisor})
start_supervised!({Sequence, [[ranges: [], step: -1], [name: :block_catchup_sequencer]]})
state = %{task_ref: nil, block_numbers: [1]}
assert {:noreply, %{task_ref: task_ref}} = Worker.handle_info(:push_front_blocks, state)
assert is_reference(task_ref)
refute_receive {^task_ref, {:error, :queue_unavailable}}
assert_receive {^task_ref, :ok}
stop_supervised(task_sup_pid)
end
end
describe "handle_info with task ref tuple" do
test "sends shutdown to supervisor on success" do
ref = Process.monitor(self())
state = %{task_ref: ref, block_numbers: [], sup_pid: self()}
Task.async(fn -> assert Worker.handle_info({ref, :ok}, state) end)
assert_receive {_, _, {:terminate, :normal}}
end
test "sends message to self to try again on failure" do
ref = Process.monitor(self())
state = %{task_ref: ref, block_numbers: [1], sup_pid: self(), retry_interval: 1}
expected_state = %{state | task_ref: nil}
assert {:noreply, ^expected_state} = Worker.handle_info({ref, {:error, :queue_unavailable}}, state)
assert_receive :push_front_blocks
end
end
describe "handle_info with failed task" do
test "sends message to self to try again" do
ref = Process.monitor(self())
state = %{task_ref: ref, block_numbers: [1], sup_pid: self(), retry_interval: 1}
assert {:noreply, %{task_ref: nil}} = Worker.handle_info({:DOWN, ref, :process, self(), :EXIT}, state)
assert_receive :push_front_blocks
end
end
end

@ -20,12 +20,6 @@ defmodule Indexer.Token.FetcherTest do
end end
describe "run/3" do describe "run/3" do
test "skips tokens that have already been cataloged", %{json_rpc_named_arguments: json_rpc_named_arguments} do
expect(EthereumJSONRPC.Mox, :json_rpc, 0, fn _, _ -> :ok end)
%Token{contract_address_hash: contract_address_hash} = insert(:token, cataloged: true)
assert Fetcher.run([contract_address_hash], json_rpc_named_arguments) == :ok
end
test "catalogs tokens that haven't been cataloged", %{json_rpc_named_arguments: json_rpc_named_arguments} do test "catalogs tokens that haven't been cataloged", %{json_rpc_named_arguments: json_rpc_named_arguments} do
token = insert(:token, name: nil, symbol: nil, total_supply: nil, decimals: nil, cataloged: false) token = insert(:token, name: nil, symbol: nil, total_supply: nil, decimals: nil, cataloged: false)
contract_address_hash = token.contract_address_hash contract_address_hash = token.contract_address_hash

Loading…
Cancel
Save