From b5bce41ab9ef5c9d8589e8f12be9777f5245d221 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Thu, 14 Nov 2019 13:07:30 +0300 Subject: [PATCH 01/16] Extend getsourcecode API view with new output fields --- CHANGELOG.md | 1 + .../views/api/rpc/contract_view.ex | 96 +++++++++++++++---- .../api/rpc/contract_controller_test.exs | 65 +++++-------- 3 files changed, 101 insertions(+), 61 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 946fcd1e45..2ca108a165 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## Current ### Features +- [#2857](https://github.com/poanetwork/blockscout/pull/2857) - Extend getsourcecode API view with new output fields - [#2787](https://github.com/poanetwork/blockscout/pull/2787) - async fetching of address counters - [#2791](https://github.com/poanetwork/blockscout/pull/2791) - add ipc client - [#2449](https://github.com/poanetwork/blockscout/pull/2449) - add ability to send notification events through postgres notify diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex index 8f996a2bcc..0d0557469b 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex @@ -35,7 +35,11 @@ defmodule BlockScoutWeb.API.RPC.ContractView do "CompilerVersion" => "", "DecompiledSourceCode" => "", "DecompilerVersion" => decompiler_version(nil), - "OptimizationUsed" => "" + "OptimizationUsed" => "", + "OptimizationRuns" => "", + "EVMVersion" => "", + "ConstructorArguments" => "", + "ExternalLibraries" => "" } end @@ -43,6 +47,64 @@ defmodule BlockScoutWeb.API.RPC.ContractView do decompiled_smart_contract = latest_decompiled_smart_contract(address.decompiled_smart_contracts) contract = address.smart_contract || %{} + optimization = Map.get(contract, :optimization, "") + + contract_output = %{ + "Address" => to_string(address.hash) + } + + contract_output + |> set_decompiled_contract_data(decompiled_smart_contract) + |> set_optimization_runs(contract, optimization) + |> set_constructor_arguments(contract) + |> set_external_libraries(contract) + |> set_verified_contract_data(contract, address, optimization) + end + + defp set_decompiled_contract_data(contract_output, decompiled_smart_contract) do + if decompiled_smart_contract do + contract_output + |> Map.put_new(:DecompiledSourceCode, decompiled_source_code(decompiled_smart_contract)) + |> Map.put_new(:DecompilerVersion, decompiler_version(decompiled_smart_contract)) + else + contract_output + end + end + + defp set_optimization_runs(contract_output, contract, optimization) do + optimization_runs = Map.get(contract, :optimization_runs, "") + + if optimization && optimization != "" do + contract_output + |> Map.put_new(:OptimizationRuns, optimization_runs) + else + contract_output + end + end + + defp set_constructor_arguments(contract_output, contract) do + constructor_arguments = Map.get(contract, :constructor_arguments, "") + + if constructor_arguments && constructor_arguments != "" do + contract_output + |> Map.put_new(:ConstructorArguments, constructor_arguments) + else + contract_output + end + end + + defp set_external_libraries(contract_output, contract) do + external_libraries = Map.get(contract, :external_libraries, []) + + if Enum.count(external_libraries) > 0 do + contract_output + |> Map.put_new(:ExternalLibraries, external_libraries) + else + contract_output + end + end + + defp set_verified_contract_data(contract_output, contract, address, optimization) do contract_abi = if is_nil(address.smart_contract) do "Contract source code not verified" @@ -51,27 +113,28 @@ defmodule BlockScoutWeb.API.RPC.ContractView do end contract_optimization = - case Map.get(contract, :optimization, "") do + case optimization do true -> - "1" + "true" false -> - "0" + "false" "" -> "" end - %{ - "Address" => to_string(address.hash), - "SourceCode" => Map.get(contract, :contract_source_code, ""), - "ABI" => contract_abi, - "ContractName" => Map.get(contract, :name, ""), - "DecompiledSourceCode" => decompiled_source_code(decompiled_smart_contract), - "DecompilerVersion" => decompiler_version(decompiled_smart_contract), - "CompilerVersion" => Map.get(contract, :compiler_version, ""), - "OptimizationUsed" => contract_optimization - } + if Map.equal?(contract, %{}) do + contract_output + else + contract_output + |> Map.put_new(:SourceCode, Map.get(contract, :contract_source_code, "")) + |> Map.put_new(:ABI, contract_abi) + |> Map.put_new(:ContractName, Map.get(contract, :name, "")) + |> Map.put_new(:CompilerVersion, Map.get(contract, :compiler_version, "")) + |> Map.put_new(:OptimizationUsed, contract_optimization) + |> Map.put_new(:EVMVersion, Map.get(contract, :evm_version, "")) + end end defp prepare_contract(%Address{ @@ -80,10 +143,7 @@ defmodule BlockScoutWeb.API.RPC.ContractView do }) do %{ "Address" => to_string(hash), - "ABI" => "Contract source code not verified", - "ContractName" => "", - "CompilerVersion" => "", - "OptimizationUsed" => "" + "ABI" => "Contract source code not verified" } end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs index 37ce57f508..f4af65e9ac 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs @@ -70,10 +70,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do assert response["result"] == [ %{ "ABI" => "Contract source code not verified", - "Address" => to_string(address.hash), - "CompilerVersion" => "", - "ContractName" => "", - "OptimizationUsed" => "" + "Address" => to_string(address.hash) } ] @@ -95,10 +92,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do assert response["result"] == [ %{ "ABI" => "Contract source code not verified", - "Address" => to_string(address.hash), - "CompilerVersion" => "", - "ContractName" => "", - "OptimizationUsed" => "" + "Address" => to_string(address.hash) } ] @@ -124,10 +118,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do assert response["result"] == [ %{ "ABI" => "Contract source code not verified", - "Address" => to_string(address.hash), - "CompilerVersion" => "", - "ContractName" => "", - "OptimizationUsed" => "" + "Address" => to_string(address.hash) } ] @@ -174,10 +165,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do assert response["result"] == [ %{ "ABI" => "Contract source code not verified", - "Address" => to_string(decompiled_smart_contract.address_hash), - "CompilerVersion" => "", - "ContractName" => "", - "OptimizationUsed" => "" + "Address" => to_string(decompiled_smart_contract.address_hash) } ] @@ -199,10 +187,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do assert response["result"] == [ %{ "ABI" => "Contract source code not verified", - "Address" => to_string(smart_contract.address_hash), - "CompilerVersion" => "", - "ContractName" => "", - "OptimizationUsed" => "" + "Address" => to_string(smart_contract.address_hash) } ] @@ -225,10 +210,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do assert %{ "ABI" => "Contract source code not verified", - "Address" => to_string(smart_contract.address_hash), - "CompilerVersion" => "", - "ContractName" => "", - "OptimizationUsed" => "" + "Address" => to_string(smart_contract.address_hash) } in response["result"] refute to_string(non_match.address_hash) in Enum.map(response["result"], &Map.get(&1, "Address")) @@ -251,10 +233,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do assert response["result"] == [ %{ "ABI" => "Contract source code not verified", - "Address" => to_string(contract_address.hash), - "CompilerVersion" => "", - "ContractName" => "", - "OptimizationUsed" => "" + "Address" => to_string(contract_address.hash) } ] @@ -281,10 +260,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do assert response["result"] == [ %{ "ABI" => "Contract source code not verified", - "Address" => to_string(contract_address.hash), - "CompilerVersion" => "", - "ContractName" => "", - "OptimizationUsed" => "" + "Address" => to_string(contract_address.hash) } ] @@ -423,7 +399,11 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do "CompilerVersion" => "", "OptimizationUsed" => "", "DecompiledSourceCode" => "", - "DecompilerVersion" => "" + "DecompilerVersion" => "", + "ConstructorArguments" => "", + "EVMVersion" => "", + "ExternalLibraries" => "", + "OptimizationRuns" => "" } ] @@ -439,7 +419,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do end test "with a verified contract address", %{conn: conn} do - contract = insert(:smart_contract, optimization: true) + contract = insert(:smart_contract, optimization: true, optimization_runs: 200, evm_version: "default") params = %{ "module" => "contract", @@ -456,12 +436,12 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do "ABI" => Jason.encode!(contract.abi), "ContractName" => contract.name, "CompilerVersion" => contract.compiler_version, - "DecompiledSourceCode" => "Contract source code not decompiled.", # The contract's optimization value is true, so the expected value # for `OptimizationUsed` is "1". If it was false, the expected value # would be "0". - "DecompilerVersion" => "", - "OptimizationUsed" => "1" + "OptimizationUsed" => "true", + "OptimizationRuns" => 200, + "EVMVersion" => "default" } ] @@ -508,9 +488,8 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do "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" + "OptimizationUsed" => "false", + "EVMVersion" => nil } assert response["status"] == "1" @@ -578,9 +557,9 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do contract_source_code assert result["ContractName"] == name - assert result["DecompiledSourceCode"] == "Contract source code not decompiled." - assert result["DecompilerVersion"] == "" - assert result["OptimizationUsed"] == "1" + assert result["DecompiledSourceCode"] == nil + assert result["DecompilerVersion"] == nil + assert result["OptimizationUsed"] == "true" assert :ok = ExJsonSchema.Validator.validate(verify_schema(), response) end end From 56f07bb85eaf54c16bf3ddc5a1144a3538d09cd0 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Fri, 29 Nov 2019 16:10:18 +0300 Subject: [PATCH 02/16] check fetched metadata in multiple places --- .../token/instance_metadata_retriever.ex | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/apps/explorer/lib/explorer/token/instance_metadata_retriever.ex b/apps/explorer/lib/explorer/token/instance_metadata_retriever.ex index bdddfde482..bde63ee1e9 100644 --- a/apps/explorer/lib/explorer/token/instance_metadata_retriever.ex +++ b/apps/explorer/lib/explorer/token/instance_metadata_retriever.ex @@ -80,7 +80,7 @@ defmodule Explorer.Token.InstanceMetadataRetriever do def fetch_json(%{"tokenURI" => {:ok, [json]}}) do {:ok, json} = decode_json(json) - {:ok, %{metadata: json}} + check_type(json) rescue e -> Logger.debug(["Unknown metadata format #{inspect(json)}. error #{inspect(e)}"], @@ -101,11 +101,7 @@ defmodule Explorer.Token.InstanceMetadataRetriever do {:ok, %Response{body: body, status_code: 200}} -> {:ok, json} = decode_json(body) - if is_map(json) do - {:ok, %{metadata: json}} - else - {:error, :wrong_metadata_type} - end + check_type(json) {:ok, %Response{body: body}} -> {:error, body} @@ -131,4 +127,12 @@ defmodule Explorer.Token.InstanceMetadataRetriever do |> Jason.decode() end end + + defp check_type(json) when is_map(json) do + {:ok, %{metadata: json}} + end + + defp check_type(_) do + {:error, :wrong_metadata_type} + end end From 36516a04dd10bc45967074beaebf9288cea2e603 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Fri, 29 Nov 2019 16:14:01 +0300 Subject: [PATCH 03/16] add CHANGELOG entry --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62ce88bd8d..89d6bada39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### Fixes +- [#2900](https://github.com/poanetwork/blockscout/pull/2900) - check fetched instance metadata in multiple places + ### Chore - [#2896](https://github.com/poanetwork/blockscout/pull/2896) - Disable Parity websockets tests From 38f2e0f7b51f3628173f677688457a616941c8cf Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Mon, 2 Dec 2019 14:49:26 +0300 Subject: [PATCH 04/16] Offset for average block time --- CHANGELOG.md | 2 + .../api/rpc/address_controller_test.exs | 15 ++++--- .../explorer/counters/average_block_time.ex | 25 ++++++----- .../counters/average_block_time_test.exs | 42 +++++++++++++++--- .../fetcher/coin_balance_on_demand_test.exs | 44 +++++++++++-------- 5 files changed, 86 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62ce88bd8d..5c670f2c27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### Fixes +- [#2902](https://github.com/poanetwork/blockscout/pull/2902) - Offset in blocks retrieval for average block time + ### Chore - [#2896](https://github.com/poanetwork/blockscout/pull/2896) - Disable Parity websockets tests diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs index 135fbe9948..5b873bed10 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs @@ -88,21 +88,24 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do mining_address = insert(:address, fetched_coin_balance: 0, - fetched_coin_balance_block_number: 2, + fetched_coin_balance_block_number: 102, inserted_at: Timex.shift(now, minutes: -10) ) mining_address_hash = to_string(mining_address.hash) # we space these very far apart so that we know it will consider the 0th block stale (it calculates how far # back we'd need to go to get 24 hours in the past) - insert(:block, number: 0, timestamp: Timex.shift(now, hours: -50), miner: mining_address) - insert(:block, number: 1, timestamp: Timex.shift(now, hours: -25), miner: mining_address) + Enum.each(0..100, fn i -> + insert(:block, number: i, timestamp: Timex.shift(now, hours: -(102 - i) * 25), miner: mining_address) + end) + + insert(:block, number: 101, timestamp: Timex.shift(now, hours: -25), miner: mining_address) AverageBlockTime.refresh() address = insert(:address, fetched_coin_balance: 100, - fetched_coin_balance_block_number: 0, + fetched_coin_balance_block_number: 100, inserted_at: Timex.shift(now, minutes: -5) ) @@ -112,7 +115,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do %{ id: id, method: "eth_getBalance", - params: [^address_hash, "0x1"] + params: [^address_hash, "0x65"] } ], _options -> @@ -148,7 +151,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do assert received_address.hash == address.hash assert received_address.fetched_coin_balance == expected_wei - assert received_address.fetched_coin_balance_block_number == 1 + assert received_address.fetched_coin_balance_block_number == 101 end end diff --git a/apps/explorer/lib/explorer/counters/average_block_time.ex b/apps/explorer/lib/explorer/counters/average_block_time.ex index 1065ede2bd..ab49650030 100644 --- a/apps/explorer/lib/explorer/counters/average_block_time.ex +++ b/apps/explorer/lib/explorer/counters/average_block_time.ex @@ -63,24 +63,25 @@ defmodule Explorer.Counters.AverageBlockTime do defp refresh_timestamps do timestamps_query = - from(block in Block, - limit: 100, - offset: 0, - order_by: [desc: block.number], - select: {block.number, block.timestamp} - ) - - query = if Application.get_env(:explorer, :include_uncles_in_average_block_time) do - timestamps_query + from(block in Block, + limit: 100, + offset: 100, + order_by: [desc: block.number], + select: {block.number, block.timestamp} + ) else - from(block in timestamps_query, - where: block.consensus == true + from(block in Block, + limit: 100, + offset: 100, + order_by: [desc: block.number], + where: block.consensus == true, + select: {block.number, block.timestamp} ) end timestamps = - query + timestamps_query |> Repo.all() |> Enum.sort_by(fn {_, timestamp} -> timestamp end, &>=/2) |> Enum.map(fn {number, timestamp} -> diff --git a/apps/explorer/test/explorer/counters/average_block_time_test.exs b/apps/explorer/test/explorer/counters/average_block_time_test.exs index 5eb00d4c73..82db1f5af4 100644 --- a/apps/explorer/test/explorer/counters/average_block_time_test.exs +++ b/apps/explorer/test/explorer/counters/average_block_time_test.exs @@ -34,11 +34,19 @@ defmodule Explorer.Counters.AverageBlockTimeTest do first_timestamp = Timex.now() - insert(:block, number: block_number, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 3)) - insert(:block, number: block_number, consensus: false, timestamp: Timex.shift(first_timestamp, seconds: 9)) - insert(:block, number: block_number, consensus: false, timestamp: Timex.shift(first_timestamp, seconds: 6)) + insert(:block, number: block_number, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: -100 - 3)) + insert(:block, number: block_number, consensus: false, timestamp: Timex.shift(first_timestamp, seconds: -100 - 9)) + insert(:block, number: block_number, consensus: false, timestamp: Timex.shift(first_timestamp, seconds: -100 - 6)) + + Enum.each(1..100, fn i -> + insert(:block, + number: block_number + i, + consensus: true, + timestamp: Timex.shift(first_timestamp, seconds: -(101 - i) - 9) + ) + end) - assert Repo.aggregate(Block, :count, :hash) == 3 + assert Repo.aggregate(Block, :count, :hash) == 103 AverageBlockTime.refresh() @@ -55,6 +63,14 @@ defmodule Explorer.Counters.AverageBlockTimeTest do insert(:block, number: block_number, consensus: false, timestamp: Timex.shift(first_timestamp, seconds: 4)) insert(:block, number: block_number + 1, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 5)) + Enum.each(1..100, fn i -> + insert(:block, + number: block_number + i + 1, + consensus: true, + timestamp: Timex.shift(first_timestamp, seconds: -(101 - i) - 5) + ) + end) + AverageBlockTime.refresh() assert AverageBlockTime.average_block_time() == Timex.Duration.parse!("PT2S") @@ -69,6 +85,14 @@ defmodule Explorer.Counters.AverageBlockTimeTest do insert(:block, number: block_number, consensus: false, timestamp: Timex.shift(first_timestamp, seconds: 4)) insert(:block, number: block_number + 1, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 5)) + Enum.each(1..100, fn i -> + insert(:block, + number: block_number + i + 1, + consensus: true, + timestamp: Timex.shift(first_timestamp, seconds: -(101 - i) - 5) + ) + end) + AverageBlockTime.refresh() assert AverageBlockTime.average_block_time() == Timex.Duration.parse!("PT1S") @@ -83,7 +107,15 @@ defmodule Explorer.Counters.AverageBlockTimeTest do insert(:block, number: block_number + 2, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 9)) insert(:block, number: block_number + 1, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 6)) - assert Repo.aggregate(Block, :count, :hash) == 3 + Enum.each(1..100, fn i -> + insert(:block, + number: block_number + i + 2, + consensus: true, + timestamp: Timex.shift(first_timestamp, seconds: -(101 - i) - 9) + ) + end) + + assert Repo.aggregate(Block, :count, :hash) == 103 AverageBlockTime.refresh() diff --git a/apps/indexer/test/indexer/fetcher/coin_balance_on_demand_test.exs b/apps/indexer/test/indexer/fetcher/coin_balance_on_demand_test.exs index 985b976a81..8f0ae262e4 100644 --- a/apps/indexer/test/indexer/fetcher/coin_balance_on_demand_test.exs +++ b/apps/indexer/test/indexer/fetcher/coin_balance_on_demand_test.exs @@ -41,15 +41,18 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemandTest do # we space these very far apart so that we know it will consider the 0th block stale (it calculates how far # back we'd need to go to get 24 hours in the past) - insert(:block, number: 0, timestamp: Timex.shift(now, hours: -50)) - insert(:block, number: 1, timestamp: now) + Enum.each(0..100, fn i -> + insert(:block, number: i, timestamp: Timex.shift(now, hours: -(101 - i) * 50)) + end) + + insert(:block, number: 101, timestamp: now) AverageBlockTime.refresh() - stale_address = insert(:address, fetched_coin_balance: 1, fetched_coin_balance_block_number: 0) - current_address = insert(:address, fetched_coin_balance: 1, fetched_coin_balance_block_number: 1) + stale_address = insert(:address, fetched_coin_balance: 1, fetched_coin_balance_block_number: 100) + current_address = insert(:address, fetched_coin_balance: 1, fetched_coin_balance_block_number: 101) - pending_address = insert(:address, fetched_coin_balance: 1, fetched_coin_balance_block_number: 1) - insert(:unfetched_balance, address_hash: pending_address.hash, block_number: 2) + pending_address = insert(:address, fetched_coin_balance: 1, fetched_coin_balance_block_number: 101) + insert(:unfetched_balance, address_hash: pending_address.hash, block_number: 102) %{stale_address: stale_address, current_address: current_address, pending_address: pending_address} end @@ -63,7 +66,7 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemandTest do test "if the address has not been fetched within the last 24 hours of blocks it is considered stale", %{ stale_address: address } do - assert CoinBalanceOnDemand.trigger_fetch(address) == {:stale, 1} + assert CoinBalanceOnDemand.trigger_fetch(address) == {:stale, 101} end test "if the address has been fetched within the last 24 hours of blocks it is considered current", %{ @@ -75,7 +78,7 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemandTest do test "if there is an unfetched balance within the window for an address, it is considered pending", %{ pending_address: pending_address } do - assert CoinBalanceOnDemand.trigger_fetch(pending_address) == {:pending, 2} + assert CoinBalanceOnDemand.trigger_fetch(pending_address) == {:pending, 102} end end @@ -88,15 +91,18 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemandTest do # we space these very far apart so that we know it will consider the 0th block stale (it calculates how far # back we'd need to go to get 24 hours in the past) - insert(:block, number: 0, timestamp: Timex.shift(now, hours: -50)) - insert(:block, number: 1, timestamp: now) + Enum.each(0..100, fn i -> + insert(:block, number: i, timestamp: Timex.shift(now, hours: -(101 - i) * 50)) + end) + + insert(:block, number: 101, timestamp: now) AverageBlockTime.refresh() :ok end test "a stale address broadcasts the new address" do - address = insert(:address, fetched_coin_balance: 1, fetched_coin_balance_block_number: 0) + address = insert(:address, fetched_coin_balance: 1, fetched_coin_balance_block_number: 100) address_hash = address.hash string_address_hash = to_string(address.hash) @@ -104,26 +110,26 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemandTest do %{ id: id, method: "eth_getBalance", - params: [^string_address_hash, "0x1"] + params: [^string_address_hash, "0x65"] } ], _options -> {:ok, [%{id: id, jsonrpc: "2.0", result: "0x02"}]} end) - assert CoinBalanceOnDemand.trigger_fetch(address) == {:stale, 1} + assert CoinBalanceOnDemand.trigger_fetch(address) == {:stale, 101} {:ok, expected_wei} = Wei.cast(2) assert_receive( {:chain_event, :addresses, :on_demand, - [%{hash: ^address_hash, fetched_coin_balance: ^expected_wei, fetched_coin_balance_block_number: 1}]} + [%{hash: ^address_hash, fetched_coin_balance: ^expected_wei, fetched_coin_balance_block_number: 101}]} ) end test "a pending address broadcasts the new address and the new coin balance" do - address = insert(:address, fetched_coin_balance: 0, fetched_coin_balance_block_number: 1) - insert(:unfetched_balance, address_hash: address.hash, block_number: 2) + address = insert(:address, fetched_coin_balance: 0, fetched_coin_balance_block_number: 101) + insert(:unfetched_balance, address_hash: address.hash, block_number: 102) address_hash = address.hash string_address_hash = to_string(address.hash) @@ -131,20 +137,20 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemandTest do %{ id: id, method: "eth_getBalance", - params: [^string_address_hash, "0x2"] + params: [^string_address_hash, "0x66"] } ], _options -> {:ok, [%{id: id, jsonrpc: "2.0", result: "0x02"}]} end) - assert CoinBalanceOnDemand.trigger_fetch(address) == {:pending, 2} + assert CoinBalanceOnDemand.trigger_fetch(address) == {:pending, 102} {:ok, expected_wei} = Wei.cast(2) assert_receive( {:chain_event, :addresses, :on_demand, - [%{hash: ^address_hash, fetched_coin_balance: ^expected_wei, fetched_coin_balance_block_number: 2}]} + [%{hash: ^address_hash, fetched_coin_balance: ^expected_wei, fetched_coin_balance_block_number: 102}]} ) end end From ddec341d950fc93b1c24252b3f35102aabf27d7b Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 2 Dec 2019 10:36:38 +0300 Subject: [PATCH 05/16] fix address sum cache on the first call address sum cache starts task to fetch a value from the DB and return nil --- CHANGELOG.md | 1 + .../api/rpc/stats_controller_test.exs | 8 ++++++++ .../lib/explorer/chain/cache/address_sum.ex | 16 +++++++++++----- .../explorer/chain/cache/address_sum_test.exs | 2 +- .../runner/address/token_balances_test.exs | 2 +- 5 files changed, 22 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62ce88bd8d..2dc425ea27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Features ### Fixes +- [#2901](https://github.com/poanetwork/blockscout/pull/2901) - fix address sum cache ### Chore diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/stats_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/stats_controller_test.exs index a57db2221b..5a7e3f29ea 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/stats_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/stats_controller_test.exs @@ -7,6 +7,12 @@ defmodule BlockScoutWeb.API.RPC.StatsControllerTest do alias Explorer.ExchangeRates.Token alias Explorer.ExchangeRates.Source.TestSource + setup do + Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.AddressSum.child_id()) + Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.AddressSum.child_id()) + :ok + end + describe "tokensupply" do test "with missing contract address", %{conn: conn} do params = %{ @@ -106,6 +112,8 @@ defmodule BlockScoutWeb.API.RPC.StatsControllerTest do describe "ethsupply" do test "returns total supply from DB", %{conn: conn} do + insert(:address, fetched_coin_balance: 6) + params = %{ "module" => "stats", "action" => "ethsupply" diff --git a/apps/explorer/lib/explorer/chain/cache/address_sum.ex b/apps/explorer/lib/explorer/chain/cache/address_sum.ex index b0d3326312..4a66e1369c 100644 --- a/apps/explorer/lib/explorer/chain/cache/address_sum.ex +++ b/apps/explorer/lib/explorer/chain/cache/address_sum.ex @@ -16,11 +16,13 @@ defmodule Explorer.Chain.Cache.AddressSum do alias Explorer.Chain defp handle_fallback(:sum) do - # This will get the task PID if one exists and launch a new task if not - # See next `handle_fallback` definition - get_async_task() + result = fetch_from_db() - {:return, nil} + if Application.get_env(:explorer, __MODULE__)[:enabled] do + {:update, result} + else + {:return, result} + end end defp handle_fallback(:async_task) do @@ -29,7 +31,7 @@ defmodule Explorer.Chain.Cache.AddressSum do {:ok, task} = Task.start(fn -> try do - result = Chain.fetch_sum_coin_total_supply() + result = fetch_from_db() set_sum(result) rescue @@ -45,6 +47,10 @@ defmodule Explorer.Chain.Cache.AddressSum do {:update, task} end + defp fetch_from_db do + Chain.fetch_sum_coin_total_supply() + end + # By setting this as a `callback` an async task will be started each time the # `sum` expires (unless there is one already running) defp async_task_on_deletion({:delete, _, :sum}), do: get_async_task() diff --git a/apps/explorer/test/explorer/chain/cache/address_sum_test.exs b/apps/explorer/test/explorer/chain/cache/address_sum_test.exs index 707c70635f..ac712c5287 100644 --- a/apps/explorer/test/explorer/chain/cache/address_sum_test.exs +++ b/apps/explorer/test/explorer/chain/cache/address_sum_test.exs @@ -12,7 +12,7 @@ defmodule Explorer.Chain.Cache.AddressSumTest do test "returns default address sum" do result = AddressSum.get_sum() - assert is_nil(result) + assert result == 0 end test "updates cache if initial value is zero" do diff --git a/apps/explorer/test/explorer/chain/import/runner/address/token_balances_test.exs b/apps/explorer/test/explorer/chain/import/runner/address/token_balances_test.exs index a40cd8599e..a35fbcc578 100644 --- a/apps/explorer/test/explorer/chain/import/runner/address/token_balances_test.exs +++ b/apps/explorer/test/explorer/chain/import/runner/address/token_balances_test.exs @@ -93,7 +93,7 @@ defmodule Explorer.Chain.Import.Runner.Address.TokenBalancesTest do value_fetched_at: DateTime.utc_now() } - run_changes(new_changes, options) |> IO.inspect() + run_changes(new_changes, options) end end From 7a1f770eae9528f5875bbe96b7dbe25910627754 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 3 Dec 2019 13:49:06 +0300 Subject: [PATCH 06/16] update CHANGELOGE --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dc425ea27..a01de32b0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ### Features ### Fixes -- [#2901](https://github.com/poanetwork/blockscout/pull/2901) - fix address sum cache +- [#2906](https://github.com/poanetwork/blockscout/pull/2906) - fix address sum cache ### Chore From bd46a309f49ca45355b30d67daff8f3f696ad053 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 3 Dec 2019 14:30:17 +0300 Subject: [PATCH 07/16] fix tests --- .../api/rpc/stats_controller_test.exs | 31 +++++++++---------- apps/explorer/lib/explorer/chain.ex | 2 +- .../lib/explorer/chain/cache/address_sum.ex | 31 +------------------ 3 files changed, 16 insertions(+), 48 deletions(-) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/stats_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/stats_controller_test.exs index 5a7e3f29ea..5dad3cc416 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/stats_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/stats_controller_test.exs @@ -1,5 +1,5 @@ defmodule BlockScoutWeb.API.RPC.StatsControllerTest do - use BlockScoutWeb.ConnCase + use BlockScoutWeb.ConnCase, async: false import Mox @@ -10,6 +10,19 @@ defmodule BlockScoutWeb.API.RPC.StatsControllerTest do setup do Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.AddressSum.child_id()) Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.AddressSum.child_id()) + + # Use TestSource mock for this test set + configuration = Application.get_env(:explorer, Explorer.ExchangeRates) + Application.put_env(:explorer, Explorer.ExchangeRates, source: TestSource) + Application.put_env(:explorer, Explorer.ExchangeRates, table_name: :rates) + Application.put_env(:explorer, Explorer.ExchangeRates, enabled: true) + + ExchangeRates.init([]) + + on_exit(fn -> + Application.put_env(:explorer, Explorer.ExchangeRates, configuration) + end) + :ok end @@ -134,22 +147,6 @@ defmodule BlockScoutWeb.API.RPC.StatsControllerTest do describe "ethprice" do setup :set_mox_global - setup do - # Use TestSource mock for this test set - configuration = Application.get_env(:explorer, Explorer.ExchangeRates) - Application.put_env(:explorer, Explorer.ExchangeRates, source: TestSource) - Application.put_env(:explorer, Explorer.ExchangeRates, table_name: :rates) - Application.put_env(:explorer, Explorer.ExchangeRates, enabled: true) - - ExchangeRates.init([]) - - :ok - - on_exit(fn -> - Application.put_env(:explorer, Explorer.ExchangeRates, configuration) - end) - end - test "returns the configured coin's price information", %{conn: conn} do symbol = Application.get_env(:explorer, :coin) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 9576eca1a6..bc2bdfd802 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -2922,7 +2922,7 @@ defmodule Explorer.Chain do """ @spec total_supply :: non_neg_integer() | nil def total_supply do - supply_module().total() + supply_module().total() || 0 end @doc """ diff --git a/apps/explorer/lib/explorer/chain/cache/address_sum.ex b/apps/explorer/lib/explorer/chain/cache/address_sum.ex index 4a66e1369c..1503740abb 100644 --- a/apps/explorer/lib/explorer/chain/cache/address_sum.ex +++ b/apps/explorer/lib/explorer/chain/cache/address_sum.ex @@ -10,8 +10,7 @@ defmodule Explorer.Chain.Cache.AddressSum do key: :sum, key: :async_task, ttl_check_interval: Application.get_env(:explorer, __MODULE__)[:ttl_check_interval], - global_ttl: Application.get_env(:explorer, __MODULE__)[:global_ttl], - callback: &async_task_on_deletion(&1) + global_ttl: Application.get_env(:explorer, __MODULE__)[:global_ttl] alias Explorer.Chain @@ -25,35 +24,7 @@ defmodule Explorer.Chain.Cache.AddressSum do end end - defp handle_fallback(:async_task) do - # If this gets called it means an async task was requested, but none exists - # so a new one needs to be launched - {:ok, task} = - Task.start(fn -> - try do - result = fetch_from_db() - - set_sum(result) - rescue - e -> - Logger.debug([ - "Coudn't update address sum test #{inspect(e)}" - ]) - end - - set_async_task(nil) - end) - - {:update, task} - end - defp fetch_from_db do Chain.fetch_sum_coin_total_supply() end - - # By setting this as a `callback` an async task will be started each time the - # `sum` expires (unless there is one already running) - defp async_task_on_deletion({:delete, _, :sum}), do: get_async_task() - - defp async_task_on_deletion(_data), do: nil end From 328b5696dcff55a9e46e78530ab378eae3e60524 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Sat, 7 Dec 2019 18:04:29 +0300 Subject: [PATCH 08/16] use default value --- CHANGELOG.md | 1 - .../api/rpc/stats_controller_test.exs | 41 ++++++++----------- .../lib/explorer/chain/cache/address_sum.ex | 41 +++++++++++++++---- .../explorer/chain/cache/address_sum_test.exs | 2 +- .../runner/address/token_balances_test.exs | 2 +- 5 files changed, 52 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a01de32b0f..62ce88bd8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,6 @@ ### Features ### Fixes -- [#2906](https://github.com/poanetwork/blockscout/pull/2906) - fix address sum cache ### Chore diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/stats_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/stats_controller_test.exs index 5dad3cc416..c20c3f128d 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/stats_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/stats_controller_test.exs @@ -1,5 +1,5 @@ defmodule BlockScoutWeb.API.RPC.StatsControllerTest do - use BlockScoutWeb.ConnCase, async: false + use BlockScoutWeb.ConnCase import Mox @@ -7,25 +7,6 @@ defmodule BlockScoutWeb.API.RPC.StatsControllerTest do alias Explorer.ExchangeRates.Token alias Explorer.ExchangeRates.Source.TestSource - setup do - Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.AddressSum.child_id()) - Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.AddressSum.child_id()) - - # Use TestSource mock for this test set - configuration = Application.get_env(:explorer, Explorer.ExchangeRates) - Application.put_env(:explorer, Explorer.ExchangeRates, source: TestSource) - Application.put_env(:explorer, Explorer.ExchangeRates, table_name: :rates) - Application.put_env(:explorer, Explorer.ExchangeRates, enabled: true) - - ExchangeRates.init([]) - - on_exit(fn -> - Application.put_env(:explorer, Explorer.ExchangeRates, configuration) - end) - - :ok - end - describe "tokensupply" do test "with missing contract address", %{conn: conn} do params = %{ @@ -125,8 +106,6 @@ defmodule BlockScoutWeb.API.RPC.StatsControllerTest do describe "ethsupply" do test "returns total supply from DB", %{conn: conn} do - insert(:address, fetched_coin_balance: 6) - params = %{ "module" => "stats", "action" => "ethsupply" @@ -137,7 +116,7 @@ defmodule BlockScoutWeb.API.RPC.StatsControllerTest do |> get("/api", params) |> json_response(200) - assert response["result"] == "6" + assert response["result"] == "0" assert response["status"] == "1" assert response["message"] == "OK" assert :ok = ExJsonSchema.Validator.validate(ethsupply_schema(), response) @@ -147,6 +126,22 @@ defmodule BlockScoutWeb.API.RPC.StatsControllerTest do describe "ethprice" do setup :set_mox_global + setup do + # Use TestSource mock for this test set + configuration = Application.get_env(:explorer, Explorer.ExchangeRates) + Application.put_env(:explorer, Explorer.ExchangeRates, source: TestSource) + Application.put_env(:explorer, Explorer.ExchangeRates, table_name: :rates) + Application.put_env(:explorer, Explorer.ExchangeRates, enabled: true) + + ExchangeRates.init([]) + + :ok + + on_exit(fn -> + Application.put_env(:explorer, Explorer.ExchangeRates, configuration) + end) + end + test "returns the configured coin's price information", %{conn: conn} do symbol = Application.get_env(:explorer, :coin) diff --git a/apps/explorer/lib/explorer/chain/cache/address_sum.ex b/apps/explorer/lib/explorer/chain/cache/address_sum.ex index 1503740abb..107932b730 100644 --- a/apps/explorer/lib/explorer/chain/cache/address_sum.ex +++ b/apps/explorer/lib/explorer/chain/cache/address_sum.ex @@ -10,21 +10,44 @@ defmodule Explorer.Chain.Cache.AddressSum do key: :sum, key: :async_task, ttl_check_interval: Application.get_env(:explorer, __MODULE__)[:ttl_check_interval], - global_ttl: Application.get_env(:explorer, __MODULE__)[:global_ttl] + global_ttl: Application.get_env(:explorer, __MODULE__)[:global_ttl], + callback: &async_task_on_deletion(&1) alias Explorer.Chain defp handle_fallback(:sum) do - result = fetch_from_db() + # This will get the task PID if one exists and launch a new task if not + # See next `handle_fallback` definition + get_async_task() - if Application.get_env(:explorer, __MODULE__)[:enabled] do - {:update, result} - else - {:return, result} - end + {:return, Decimal.new(0)} end - defp fetch_from_db do - Chain.fetch_sum_coin_total_supply() + defp handle_fallback(:async_task) do + # If this gets called it means an async task was requested, but none exists + # so a new one needs to be launched + {:ok, task} = + Task.start(fn -> + try do + result = Chain.fetch_sum_coin_total_supply() + + set_sum(result) + rescue + e -> + Logger.debug([ + "Coudn't update address sum test #{inspect(e)}" + ]) + end + + set_async_task(nil) + end) + + {:update, task} end + + # By setting this as a `callback` an async task will be started each time the + # `sum` expires (unless there is one already running) + defp async_task_on_deletion({:delete, _, :sum}), do: get_async_task() + + defp async_task_on_deletion(_data), do: nil end diff --git a/apps/explorer/test/explorer/chain/cache/address_sum_test.exs b/apps/explorer/test/explorer/chain/cache/address_sum_test.exs index ac712c5287..9d7cedaec0 100644 --- a/apps/explorer/test/explorer/chain/cache/address_sum_test.exs +++ b/apps/explorer/test/explorer/chain/cache/address_sum_test.exs @@ -12,7 +12,7 @@ defmodule Explorer.Chain.Cache.AddressSumTest do test "returns default address sum" do result = AddressSum.get_sum() - assert result == 0 + assert result == Decimal.new(0) end test "updates cache if initial value is zero" do diff --git a/apps/explorer/test/explorer/chain/import/runner/address/token_balances_test.exs b/apps/explorer/test/explorer/chain/import/runner/address/token_balances_test.exs index a35fbcc578..a40cd8599e 100644 --- a/apps/explorer/test/explorer/chain/import/runner/address/token_balances_test.exs +++ b/apps/explorer/test/explorer/chain/import/runner/address/token_balances_test.exs @@ -93,7 +93,7 @@ defmodule Explorer.Chain.Import.Runner.Address.TokenBalancesTest do value_fetched_at: DateTime.utc_now() } - run_changes(new_changes, options) + run_changes(new_changes, options) |> IO.inspect() end end From e7c318f11a6791a035542274daee74cb03dcdf81 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Sat, 7 Dec 2019 18:05:01 +0300 Subject: [PATCH 09/16] fix --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62ce88bd8d..a01de32b0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Features ### Fixes +- [#2906](https://github.com/poanetwork/blockscout/pull/2906) - fix address sum cache ### Chore From 2d59ff4d22887aed5ea37389237bcfafa32d8c71 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Sun, 8 Dec 2019 15:57:16 +0300 Subject: [PATCH 10/16] Reduce execution time of stream_unfetched_token_instances query --- CHANGELOG.md | 3 +-- apps/explorer/lib/explorer/chain.ex | 20 ++++++++++++++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92c76dff99..1e5daec994 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,12 +3,11 @@ ### Features ### Fixes +- [#2914](https://github.com/poanetwork/blockscout/pull/2914) - Reduce execution time of stream_unfetched_token_instances query - [#2906](https://github.com/poanetwork/blockscout/pull/2906) - fix address sum cache - - [#2902](https://github.com/poanetwork/blockscout/pull/2902) - Offset in blocks retrieval for average block time ### Chore - - [#2896](https://github.com/poanetwork/blockscout/pull/2896) - Disable Parity websockets tests diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index bc2bdfd802..2e10d28ea0 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -2967,21 +2967,33 @@ defmodule Explorer.Chain do ) :: {:ok, accumulator} when accumulator: term() def stream_unfetched_token_instances(initial, reducer) when is_function(reducer, 2) do + nft_tokens = + from( + token in Token, + where: token.type == ^"ERC-721", + select: token.contract_address_hash + ) + query = from( token_transfer in TokenTransfer, - inner_join: token in Token, + inner_join: token in subquery(nft_tokens), on: token.contract_address_hash == token_transfer.token_contract_address_hash, left_join: instance in Instance, on: token_transfer.token_id == instance.token_id and token_transfer.token_contract_address_hash == instance.token_contract_address_hash, - where: token.type == ^"ERC-721" and is_nil(instance.token_id) and not is_nil(token_transfer.token_id), - distinct: [token_transfer.token_contract_address_hash, token_transfer.token_id], + where: is_nil(instance.token_id) and not is_nil(token_transfer.token_id), select: %{contract_address_hash: token_transfer.token_contract_address_hash, token_id: token_transfer.token_id} ) - Repo.stream_reduce(query, initial, reducer) + distinct_query = + from( + q in subquery(query), + distinct: [q.token_contract_address_hash, q.token_id] + ) + + Repo.stream_reduce(distinct_query, initial, reducer) end @doc """ From bde993f75dd01ab9f5e71bffadda8d2959e4e39b Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Tue, 10 Dec 2019 15:52:10 +0300 Subject: [PATCH 11/16] Add additional tests --- .../views/api/rpc/contract_view.ex | 7 +- .../api/rpc/contract_controller_test.exs | 145 ++++++++++++++++++ 2 files changed, 151 insertions(+), 1 deletion(-) diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex index 0d0557469b..e22a445296 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex @@ -97,8 +97,13 @@ defmodule BlockScoutWeb.API.RPC.ContractView do external_libraries = Map.get(contract, :external_libraries, []) if Enum.count(external_libraries) > 0 do + external_libraries_without_id = + Enum.map(external_libraries, fn %{name: name, address_hash: address_hash} -> + %{"name" => name, "address_hash" => address_hash} + end) + contract_output - |> Map.put_new(:ExternalLibraries, external_libraries) + |> Map.put_new(:ExternalLibraries, external_libraries_without_id) else contract_output end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs index f4af65e9ac..5c12c1e5db 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs @@ -1,5 +1,6 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do use BlockScoutWeb.ConnCase + alias Explorer.Chain.SmartContract alias Explorer.{Chain, Factory} describe "listcontracts" do @@ -455,6 +456,150 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do assert response["message"] == "OK" assert :ok = ExJsonSchema.Validator.validate(getsourcecode_schema(), response) end + + test "with constructor arguments", %{conn: conn} do + contract = + insert(:smart_contract, + optimization: true, + optimization_runs: 200, + evm_version: "default", + constructor_arguments: + "00000000000000000000000008e7592ce0d7ebabf42844b62ee6a878d4e1913e000000000000000000000000e1b6037da5f1d756499e184ca15254a981c92546" + ) + + params = %{ + "module" => "contract", + "action" => "getsourcecode", + "address" => to_string(contract.address_hash) + } + + expected_result = [ + %{ + "Address" => to_string(contract.address_hash), + "SourceCode" => + "/**\n* Submitted for verification at blockscout.com on #{contract.inserted_at}\n*/\n" <> + contract.contract_source_code, + "ABI" => Jason.encode!(contract.abi), + "ContractName" => contract.name, + "CompilerVersion" => contract.compiler_version, + "OptimizationUsed" => "true", + "OptimizationRuns" => 200, + "EVMVersion" => "default", + "ConstructorArguments" => + "00000000000000000000000008e7592ce0d7ebabf42844b62ee6a878d4e1913e000000000000000000000000e1b6037da5f1d756499e184ca15254a981c92546" + } + ] + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(getsourcecode_schema(), response) + end + + test "with external library", %{conn: conn} do + smart_contract_bytecode = + "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582040d82a7379b1ee1632ad4d8a239954fd940277b25628ead95259a85c5eddb2120029" + + created_contract_address = + insert( + :address, + hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c", + contract_code: smart_contract_bytecode + ) + + transaction = + :transaction + |> insert() + |> with_block() + + insert( + :internal_transaction_create, + transaction: transaction, + index: 0, + created_contract_address: created_contract_address, + created_contract_code: smart_contract_bytecode, + block_number: transaction.block_number, + transaction_index: transaction.index + ) + + valid_attrs = %{ + address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c", + name: "Test", + compiler_version: "0.4.23", + contract_source_code: + "pragma solidity ^0.4.23; contract SimpleStorage {uint storedData; function set(uint x) public {storedData = x; } function get() public constant returns (uint) {return storedData; } }", + abi: [ + %{ + "constant" => false, + "inputs" => [%{"name" => "x", "type" => "uint256"}], + "name" => "set", + "outputs" => [], + "payable" => false, + "stateMutability" => "nonpayable", + "type" => "function" + }, + %{ + "constant" => true, + "inputs" => [], + "name" => "get", + "outputs" => [%{"name" => "", "type" => "uint256"}], + "payable" => false, + "stateMutability" => "view", + "type" => "function" + } + ], + optimization: true, + optimization_runs: 200, + evm_version: "default" + } + + external_libraries = [ + %SmartContract.ExternalLibrary{:address_hash => "0xb18aed9518d735482badb4e8b7fd8d2ba425ce95", :name => "Test"}, + %SmartContract.ExternalLibrary{:address_hash => "0x283539e1b1daf24cdd58a3e934d55062ea663c3f", :name => "Test2"} + ] + + {:ok, %SmartContract{} = contract} = Chain.create_smart_contract(valid_attrs, external_libraries) + + params = %{ + "module" => "contract", + "action" => "getsourcecode", + "address" => to_string(contract.address_hash) + } + + expected_result = [ + %{ + "Address" => to_string(contract.address_hash), + "SourceCode" => + "/**\n* Submitted for verification at blockscout.com on #{contract.inserted_at}\n*/\n" <> + contract.contract_source_code, + "ABI" => Jason.encode!(contract.abi), + "ContractName" => contract.name, + "CompilerVersion" => contract.compiler_version, + "OptimizationUsed" => "true", + "OptimizationRuns" => 200, + "EVMVersion" => "default", + "ExternalLibraries" => [ + %{"name" => "Test", "address_hash" => "0xb18aed9518d735482badb4e8b7fd8d2ba425ce95"}, + %{"name" => "Test2", "address_hash" => "0x283539e1b1daf24cdd58a3e934d55062ea663c3f"} + ] + } + ] + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + assert :ok = ExJsonSchema.Validator.validate(getsourcecode_schema(), response) + end end describe "verify" do From 30132a71a9e17173d67629ec1972be51c82ab28b Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Tue, 10 Dec 2019 16:31:27 +0300 Subject: [PATCH 12/16] CR issues --- .../block_scout_web/views/api/rpc/contract_view.ex | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex index e22a445296..cd0c0a7af5 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex @@ -82,17 +82,13 @@ defmodule BlockScoutWeb.API.RPC.ContractView do end end - defp set_constructor_arguments(contract_output, contract) do - constructor_arguments = Map.get(contract, :constructor_arguments, "") - - if constructor_arguments && constructor_arguments != "" do - contract_output - |> Map.put_new(:ConstructorArguments, constructor_arguments) - else - contract_output - end + defp set_constructor_arguments(contract_output, %{constructor_arguments: constructor_arguments}) do + contract_output + |> Map.put_new(:ConstructorArguments, constructor_arguments) end + defp set_constructor_arguments(contract_output, _), do: contract_output + defp set_external_libraries(contract_output, contract) do external_libraries = Map.get(contract, :external_libraries, []) From 4ed9fdd2c98205cfefe522ba26cfb6231f20d7bb Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Tue, 10 Dec 2019 17:04:03 +0300 Subject: [PATCH 13/16] Add guard is_empty_string --- .../block_scout_web/views/api/rpc/contract_view.ex | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex index cd0c0a7af5..7d92c8d5a2 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex @@ -4,6 +4,8 @@ defmodule BlockScoutWeb.API.RPC.ContractView do alias BlockScoutWeb.API.RPC.RPCView alias Explorer.Chain.{Address, DecompiledSmartContract, SmartContract} + defguardp is_empty_string(input) when input == "" or input == nil + def render("listcontracts.json", %{contracts: contracts}) do contracts = Enum.map(contracts, &prepare_contract/1) @@ -82,13 +84,14 @@ defmodule BlockScoutWeb.API.RPC.ContractView do end end - defp set_constructor_arguments(contract_output, %{constructor_arguments: constructor_arguments}) do + defp set_constructor_arguments(contract_output, %{constructor_arguments: arguments}) when is_empty_string(arguments), + do: contract_output + + defp set_constructor_arguments(contract_output, %{constructor_arguments: arguments}) do contract_output - |> Map.put_new(:ConstructorArguments, constructor_arguments) + |> Map.put_new(:ConstructorArguments, arguments) end - defp set_constructor_arguments(contract_output, _), do: contract_output - defp set_external_libraries(contract_output, contract) do external_libraries = Map.get(contract, :external_libraries, []) From a7d9f0fcf2235bb27e666d1c6a6ea1df596f1f59 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Tue, 10 Dec 2019 17:48:53 +0300 Subject: [PATCH 14/16] Fix selected column name --- apps/explorer/lib/explorer/chain.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 2e10d28ea0..401d3b8cbe 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -2990,7 +2990,7 @@ defmodule Explorer.Chain do distinct_query = from( q in subquery(query), - distinct: [q.token_contract_address_hash, q.token_id] + distinct: [q.contract_address_hash, q.token_id] ) Repo.stream_reduce(distinct_query, initial, reducer) From 06c781d94e311e9141cb9cc1550e353cff0f670e Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Tue, 10 Dec 2019 18:10:48 +0300 Subject: [PATCH 15/16] Missing function clause for set_constructor_arguments --- .../lib/block_scout_web/views/api/rpc/contract_view.ex | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex index 7d92c8d5a2..41a0b7734d 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex @@ -92,6 +92,8 @@ defmodule BlockScoutWeb.API.RPC.ContractView do |> Map.put_new(:ConstructorArguments, arguments) end + defp set_constructor_arguments(contract_output, _), do: contract_output + defp set_external_libraries(contract_output, contract) do external_libraries = Map.get(contract, :external_libraries, []) From 92e496365136e43a25240f8df85f3ceafa0ed212 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Tue, 10 Dec 2019 18:58:51 +0300 Subject: [PATCH 16/16] Add tokenID for tokentx action --- CHANGELOG.md | 3 +-- .../lib/block_scout_web/etherscan.ex | 7 +++++++ .../views/api/rpc/address_view.ex | 19 +++++++++++++------ .../api/rpc/address_controller_test.exs | 3 ++- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d8b458a9d..83af597aa3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,13 @@ ## Current ### Features +- [#2918](https://github.com/poanetwork/blockscout/pull/2918) - Add tokenID for tokentx API action ### Fixes - [#2906](https://github.com/poanetwork/blockscout/pull/2906) - fix address sum cache - - [#2902](https://github.com/poanetwork/blockscout/pull/2902) - Offset in blocks retrieval for average block time ### Chore - - [#2896](https://github.com/poanetwork/blockscout/pull/2896) - Disable Parity websockets tests diff --git a/apps/block_scout_web/lib/block_scout_web/etherscan.ex b/apps/block_scout_web/lib/block_scout_web/etherscan.ex index abb358179c..5e6c05bb41 100644 --- a/apps/block_scout_web/lib/block_scout_web/etherscan.ex +++ b/apps/block_scout_web/lib/block_scout_web/etherscan.ex @@ -589,6 +589,12 @@ defmodule BlockScoutWeb.Etherscan do example: ~s("Some Token Name") } + @token_id_type %{ + type: "integer", + definition: "id of token", + example: ~s("0") + } + @token_symbol_type %{ type: "string", definition: "Trading symbol of the token.", @@ -752,6 +758,7 @@ defmodule BlockScoutWeb.Etherscan do example: ~s("663046792267785498951364") }, tokenName: @token_name_type, + tokenID: @token_id_type, tokenSymbol: @token_symbol_type, tokenDecimal: @token_decimal_type, transactionIndex: @transaction_index_type, diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex index 8063b1db40..62da759428 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex @@ -121,7 +121,7 @@ defmodule BlockScoutWeb.API.RPC.AddressView do } end - defp prepare_token_transfer(token_transfer) do + defp prepare_common_token_transfer(token_transfer) do %{ "blockNumber" => to_string(token_transfer.block_number), "timeStamp" => to_string(DateTime.to_unix(token_transfer.block_timestamp)), @@ -132,7 +132,6 @@ defmodule BlockScoutWeb.API.RPC.AddressView do "contractAddress" => to_string(token_transfer.token_contract_address_hash), "to" => to_string(token_transfer.to_address_hash), "logIndex" => to_string(token_transfer.token_log_index), - "value" => get_token_value(token_transfer), "tokenName" => token_transfer.token_name, "tokenSymbol" => token_transfer.token_symbol, "tokenDecimal" => to_string(token_transfer.token_decimals), @@ -146,12 +145,20 @@ defmodule BlockScoutWeb.API.RPC.AddressView do } end - defp get_token_value(%{token_type: "ERC-721"} = token_transfer) do - to_string(token_transfer.token_id) + defp prepare_token_transfer(%{token_type: "ERC-721"} = token_transfer) do + token_transfer + |> prepare_common_token_transfer() + |> Map.put_new(:tokenID, token_transfer.token_id) + end + + defp prepare_token_transfer(%{token_type: "ERC-20"} = token_transfer) do + token_transfer + |> prepare_common_token_transfer() + |> Map.put_new(:value, to_string(token_transfer.amount)) end - defp get_token_value(token_transfer) do - to_string(token_transfer.amount) + defp prepare_token_transfer(token_transfer) do + prepare_common_token_transfer(token_transfer) end defp prepare_block(block) do diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs index 5b873bed10..2db1bdb33c 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs @@ -1807,7 +1807,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do |> get("/api", params) |> json_response(200) - assert result["value"] == to_string(token_transfer.token_id) + assert result["tokenID"] == to_string(token_transfer.token_id) assert response["status"] == "1" assert response["message"] == "OK" assert :ok = ExJsonSchema.Validator.validate(tokentx_schema(), response) @@ -2618,6 +2618,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do "logIndex" => %{"type" => "string"}, "value" => %{"type" => "string"}, "tokenName" => %{"type" => "string"}, + "tokenID" => %{"type" => "string"}, "tokenSymbol" => %{"type" => "string"}, "tokenDecimal" => %{"type" => "string"}, "transactionIndex" => %{"type" => "string"},