From 3c4d63149c4816a6ddc1b285a2a04f7670340aef Mon Sep 17 00:00:00 2001 From: saneery Date: Tue, 7 May 2019 13:06:44 +0300 Subject: [PATCH 01/68] mark pools as deleted if they don't exist in the list --- .../chain/import/runner/staking_pools.ex | 29 +++++++++++++++++++ .../lib/indexer/fetcher/staking_pools.ex | 1 + 2 files changed, 30 insertions(+) diff --git a/apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex b/apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex index aaf5d7242e..f0e62b7e89 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex @@ -41,6 +41,9 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do |> Map.put(:timestamps, timestamps) multi + |> Multi.run(:mark_as_deleted, fn repo, _ -> + mark_as_deleted(repo, changes_list, insert_options) + end) |> Multi.run(:insert_staking_pools, fn repo, _ -> insert(repo, changes_list, insert_options) end) @@ -49,6 +52,32 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do @impl Import.Runner def timeout, do: @timeout + defp mark_as_deleted(repo, changes_list, %{timeout: timeout}) when is_list(changes_list) do + addresses = Enum.map(changes_list, & &1.address_hash) + + query = + from( + address_name in Address.Name, + where: + address_name.address_hash not in ^addresses and + fragment("(metadata->>'is_pool')::boolean = true"), + update: [ + set: [ + metadata: fragment("metadata || '{\"deleted\": true}'::jsonb") + ] + ] + ) + + try do + {_, result} = repo.update_all(query, [], timeout: timeout) + + {:ok, result} + rescue + postgrex_error in Postgrex.Error -> + {:error, %{exception: postgrex_error}} + end + end + @spec insert(Repo.t(), [map()], %{ optional(:on_conflict) => Import.Runner.on_conflict(), required(:timeout) => timeout, diff --git a/apps/indexer/lib/indexer/fetcher/staking_pools.ex b/apps/indexer/lib/indexer/fetcher/staking_pools.ex index 68794d9ee4..fe4ab84c28 100644 --- a/apps/indexer/lib/indexer/fetcher/staking_pools.ex +++ b/apps/indexer/lib/indexer/fetcher/staking_pools.ex @@ -125,6 +125,7 @@ defmodule Indexer.Fetcher.StakingPools do pool |> Map.delete(:staking_address) |> Map.put(:mining_address, mining_address) + |> Map.put(:is_pool, true) %{ name: "anonymous", From 3705ba2d1e1b6e4acd9384206c60fdcc9c12d6a2 Mon Sep 17 00:00:00 2001 From: saneery Date: Tue, 7 May 2019 13:09:44 +0300 Subject: [PATCH 02/68] get pool's stake amount that been made by the pool --- .../lib/explorer/staking/pools_reader.ex | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/apps/explorer/lib/explorer/staking/pools_reader.ex b/apps/explorer/lib/explorer/staking/pools_reader.ex index de03ff10b5..e3bb27084f 100644 --- a/apps/explorer/lib/explorer/staking/pools_reader.ex +++ b/apps/explorer/lib/explorer/staking/pools_reader.ex @@ -29,6 +29,7 @@ defmodule Explorer.Staking.PoolsReader do {:ok, [delegator_addresses]} <- data["poolDelegators"], delegators_count = Enum.count(delegator_addresses), {:ok, [staked_amount]} <- data["stakeAmountTotalMinusOrderedWithdraw"], + {:ok, [self_staked_amount]} <- data["stakeAmountMinusOrderedWithdraw"], {:ok, [is_validator]} <- data["isValidator"], {:ok, [was_validator_count]} <- data["validatorCounter"], {:ok, [is_banned]} <- data["isValidatorBanned"], @@ -42,6 +43,7 @@ defmodule Explorer.Staking.PoolsReader do is_active: is_active, delegators_count: delegators_count, staked_amount: staked_amount, + self_staked_amount: self_staked_amount, is_validator: is_validator, was_validator_count: was_validator_count, is_banned: is_banned, @@ -77,14 +79,15 @@ defmodule Explorer.Staking.PoolsReader do contract_abi = abi("staking.json") ++ abi("validators.json") methods = [ - {:staking, "isPoolActive", staking_address}, - {:staking, "poolDelegators", staking_address}, - {:staking, "stakeAmountTotalMinusOrderedWithdraw", staking_address}, - {:validators, "isValidator", mining_address}, - {:validators, "validatorCounter", mining_address}, - {:validators, "isValidatorBanned", mining_address}, - {:validators, "bannedUntil", mining_address}, - {:validators, "banCounter", mining_address} + {:staking, "isPoolActive", [staking_address]}, + {:staking, "poolDelegators", [staking_address]}, + {:staking, "stakeAmountTotalMinusOrderedWithdraw", [staking_address]}, + {:staking, "stakeAmountMinusOrderedWithdraw", [staking_address, staking_address]}, + {:validators, "isValidator", [mining_address]}, + {:validators, "validatorCounter", [mining_address]}, + {:validators, "isValidatorBanned", [mining_address]}, + {:validators, "bannedUntil", [mining_address]}, + {:validators, "banCounter", [mining_address]}, ] methods @@ -96,11 +99,11 @@ defmodule Explorer.Staking.PoolsReader do end) end - defp format_request({contract_name, function_name, param}) do + defp format_request({contract_name, function_name, params}) do %{ contract_address: contract(contract_name), function_name: function_name, - args: [param] + args: params } end From fbe4852b37e229b2cb55fc084d532ad14ea577fb Mon Sep 17 00:00:00 2001 From: saneery Date: Wed, 8 May 2019 11:06:10 +0300 Subject: [PATCH 03/68] add functions to getting pools --- apps/explorer/lib/explorer/chain.ex | 44 +++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index e6fb9c011e..840b2fb288 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -10,6 +10,7 @@ defmodule Explorer.Chain do limit: 2, order_by: 2, order_by: 3, + offset: 2, preload: 2, select: 2, subquery: 1, @@ -2831,6 +2832,49 @@ defmodule Explorer.Chain do value end + @doc """ + List of staking pools which are validators + """ + @spec validators_pools(lim :: integer, off :: integer) :: [map] + def validators_pools(lim, off) when is_integer(lim) and is_integer(off) do + Address.Name + |> where( + [_], + fragment(""" + (metadata->>'is_active')::boolean = true and + (metadata->>'deleted')::boolean is not true and + (metadata->>'is_validator')::boolean = true + """) + ) + |> limit(^lim) + |> offset(^off) + |> Repo.all() + end + + @doc """ + List of active pools + """ + @spec active_pools(lim :: integer, off :: integer) :: [map] + def active_pools(lim, off) when is_integer(lim) and is_integer(off) do + Address.Name + |> where([_], fragment("(metadata->>'is_active')::boolean = true")) + |> limit(^lim) + |> offset(^off) + |> Repo.all() + end + + @doc """ + List of inactive pools + """ + @spec inactive_pools(lim :: integer, off :: integer) :: [map] + def inactive_pools(lim, off) when is_integer(lim) and is_integer(off) do + Address.Name + |> where([_], fragment("(metadata->>'is_active')::boolean = false")) + |> limit(^lim) + |> offset(^off) + |> Repo.all() + end + defp with_decompiled_code_flag(query, hash) do has_decompiled_code_query = from(decompiled_contract in DecompiledSmartContract, From 7c9fe671a4b6451818e6b449c54a0350674a6fb5 Mon Sep 17 00:00:00 2001 From: saneery Date: Wed, 8 May 2019 14:28:11 +0300 Subject: [PATCH 04/68] fetching staking epoch info --- apps/explorer/config/config.exs | 6 +- apps/explorer/lib/explorer/application.ex | 7 +- .../lib/explorer/staking/epoch_counter.ex | 118 +++ .../priv/contracts_abi/pos/staking.json | 802 ++++++++++-------- 4 files changed, 590 insertions(+), 343 deletions(-) create mode 100644 apps/explorer/lib/explorer/staking/epoch_counter.ex diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index ace69884ea..c1981fcde8 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -58,6 +58,10 @@ config :explorer, Explorer.Staking.PoolsReader, validators_contract_address: System.get_env("POS_VALIDATORS_CONTRACT"), staking_contract_address: System.get_env("POS_STAKING_CONTRACT") +config :explorer, Explorer.Staking.EpochCounter, + enabled: false, + staking_contract_address: System.get_env("POS_STAKING_CONTRACT") + if System.get_env("SUPPLY_MODULE") == "TokenBridge" do config :explorer, supply: Explorer.Chain.Supply.TokenBridge end @@ -82,8 +86,6 @@ config :spandex_ecto, SpandexEcto.EctoLogger, tracer: Explorer.Tracer, otp_app: :explorer -config :explorer, Explorer.Chain.BlockCountCache, ttl: System.get_env("BLOCK_COUNT_CACHE_TTL") - # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{Mix.env()}.exs" diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index b4f8589a57..d886dd8a0b 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -29,7 +29,8 @@ defmodule Explorer.Application do Explorer.SmartContract.SolcDownloader, {Registry, keys: :duplicate, name: Registry.ChainEvents, id: Registry.ChainEvents}, {Admin.Recovery, [[], [name: Admin.Recovery]]}, - {TransactionCountCache, [[], []]} + {TransactionCountCache, [[], []]}, + {BlockCountCache, []} ] children = base_children ++ configurable_children() @@ -39,7 +40,6 @@ defmodule Explorer.Application do res = Supervisor.start_link(children, opts) BlockNumberCache.setup() - BlockCountCache.setup() res end @@ -51,7 +51,8 @@ defmodule Explorer.Application do configure(Explorer.Market.History.Cataloger), configure(Explorer.Counters.AddressesWithBalanceCounter), configure(Explorer.Counters.AverageBlockTime), - configure(Explorer.Validator.MetadataProcessor) + configure(Explorer.Validator.MetadataProcessor), + configure(Explorer.Staking.EpochCounter) ] |> List.flatten() end diff --git a/apps/explorer/lib/explorer/staking/epoch_counter.ex b/apps/explorer/lib/explorer/staking/epoch_counter.ex new file mode 100644 index 0000000000..256bc6fe0b --- /dev/null +++ b/apps/explorer/lib/explorer/staking/epoch_counter.ex @@ -0,0 +1,118 @@ +defmodule Explorer.Staking.EpochCounter do + @moduledoc """ + Counts staking epoch + """ + + use GenServer + + alias Explorer.Chain.Events.Subscriber + alias Explorer.SmartContract.Reader + + @table_name __MODULE__ + @epoch_key "epoch_num" + @epoch_end_key "epoch_end_block" + + @doc "Current staking epoch number" + def epoch_number do + case :ets.lookup(@table_name, @epoch_key) do + [{_, epoch_num}] -> + epoch_num + + _ -> + 0 + end + end + + @doc "Block number on which will start new epoch" + def epoch_end_block do + case :ets.lookup(@table_name, @epoch_end_key) do + [{_, epoch_end}] -> + epoch_end + + _ -> + 0 + end + end + + def start_link([]) do + GenServer.start_link(__MODULE__, [], name: __MODULE__) + end + + def init([]) do + if :ets.whereis(@table_name) == :undefined do + :ets.new(@table_name, [ + :set, + :named_table, + :public, + write_concurrency: true + ]) + end + + Subscriber.to(:blocks, :realtime) + {:ok, [], {:continue, :epoch_info}} + end + + def handle_continue(:epoch_info, state) do + fetch_epoch_info() + {:noreply, state} + end + + def handle_info({:chain_event, :blocks, :realtime, blocks}, state) do + new_block = Enum.max_by(blocks, &Map.get(&1, :number), fn -> 0 end) + block_number = new_block.number + + case :ets.lookup(@table_name, @epoch_end_key) do + [] -> + fetch_epoch_info() + + [{_, epoch_end_block}] when epoch_end_block < block_number -> + fetch_epoch_info() + + _ -> + :ok + end + + {:noreply, state} + end + + defp fetch_epoch_info do + with data <- get_epoch_info(), + {:ok, [epoch_num]} <- data["stakingEpoch"], + {:ok, [epoch_end_block]} <- data["stakingEpochEndBlock"] do + :ets.insert(@table_name, {@epoch_key, epoch_num}) + :ets.insert(@table_name, {@epoch_end_key, epoch_end_block}) + end + end + + defp get_epoch_info do + contract_abi = abi("staking.json") + + functions = ["stakingEpoch", "stakingEpochEndBlock"] + + functions + |> Enum.map(fn function -> + %{ + contract_address: staking_address(), + function_name: function, + args: [] + } + end) + |> Reader.query_contracts(contract_abi) + |> Enum.zip(functions) + |> Enum.into(%{}, fn {response, function} -> + {function, response} + end) + end + + defp staking_address do + Application.get_env(:explorer, __MODULE__, [])[:staking_contract_address] + end + + # sobelow_skip ["Traversal"] + defp abi(file_name) do + :explorer + |> Application.app_dir("priv/contracts_abi/pos/#{file_name}") + |> File.read!() + |> Jason.decode!() + end +end diff --git a/apps/explorer/priv/contracts_abi/pos/staking.json b/apps/explorer/priv/contracts_abi/pos/staking.json index 7bcbcfb18c..33f773ea61 100644 --- a/apps/explorer/priv/contracts_abi/pos/staking.json +++ b/apps/explorer/priv/contracts_abi/pos/staking.json @@ -1,36 +1,17 @@ [ { "constant": true, - "inputs": [], - "name": "STAKE_UNIT", - "outputs": [ + "inputs": [ { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "MAX_DELEGATORS_PER_POOL", - "outputs": [ + "name": "_poolStakingAddress", + "type": "address" + }, { - "name": "", - "type": "uint256" + "name": "_delegator", + "type": "address" } ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "MAX_CANDIDATES", + "name": "poolDelegatorIndex", "outputs": [ { "name": "", @@ -42,154 +23,55 @@ "type": "function" }, { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "fromPoolStakingAddress", - "type": "address" - }, - { - "indexed": true, - "name": "staker", - "type": "address" - }, - { - "indexed": true, - "name": "stakingEpoch", - "type": "uint256" - }, - { - "indexed": false, - "name": "amount", - "type": "uint256" - } - ], - "name": "Claimed", - "type": "event" - }, - { - "anonymous": false, + "constant": true, "inputs": [ { - "indexed": true, - "name": "toPoolStakingAddress", - "type": "address" - }, - { - "indexed": true, - "name": "staker", + "name": "_poolStakingAddress", "type": "address" - }, - { - "indexed": true, - "name": "stakingEpoch", - "type": "uint256" - }, - { - "indexed": false, - "name": "amount", - "type": "uint256" } ], - "name": "Staked", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "fromPoolStakingAddress", - "type": "address" - }, - { - "indexed": true, - "name": "toPoolStakingAddress", - "type": "address" - }, - { - "indexed": true, - "name": "staker", - "type": "address" - }, - { - "indexed": true, - "name": "stakingEpoch", - "type": "uint256" - }, + "name": "stakeAmountTotalMinusOrderedWithdraw", + "outputs": [ { - "indexed": false, - "name": "amount", + "name": "", "type": "uint256" } ], - "name": "StakeMoved", - "type": "event" + "payable": false, + "stateMutability": "view", + "type": "function" }, { - "anonymous": false, + "constant": false, "inputs": [ { - "indexed": true, - "name": "fromPoolStakingAddress", - "type": "address" - }, - { - "indexed": true, - "name": "staker", + "name": "_erc20TokenContract", "type": "address" - }, - { - "indexed": true, - "name": "stakingEpoch", - "type": "uint256" - }, - { - "indexed": false, - "name": "amount", - "type": "int256" } ], - "name": "WithdrawalOrdered", - "type": "event" + "name": "setErc20TokenContract", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" }, { - "anonymous": false, + "constant": false, "inputs": [ { - "indexed": true, - "name": "fromPoolStakingAddress", + "name": "_fromPoolStakingAddress", "type": "address" }, { - "indexed": true, - "name": "staker", + "name": "_toPoolStakingAddress", "type": "address" }, { - "indexed": true, - "name": "stakingEpoch", - "type": "uint256" - }, - { - "indexed": false, - "name": "amount", + "name": "_amount", "type": "uint256" } ], - "name": "Withdrawn", - "type": "event" - }, - { - "constant": false, - "inputs": [ - { - "name": "_unremovableStakingAddress", - "type": "address" - } - ], - "name": "clearUnremovableValidator", + "name": "moveStake", "outputs": [], "payable": false, "stateMutability": "nonpayable", @@ -197,8 +79,13 @@ }, { "constant": false, - "inputs": [], - "name": "incrementStakingEpoch", + "inputs": [ + { + "name": "_minStake", + "type": "uint256" + } + ], + "name": "setDelegatorMinStake", "outputs": [], "payable": false, "stateMutability": "nonpayable", @@ -231,132 +118,75 @@ "constant": false, "inputs": [ { - "name": "_fromPoolStakingAddress", - "type": "address" - }, - { - "name": "_toPoolStakingAddress", - "type": "address" - }, - { - "name": "_amount", + "name": "_minStake", "type": "uint256" } ], - "name": "moveStake", + "name": "setCandidateMinStake", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { - "constant": false, + "constant": true, "inputs": [ { - "name": "_toPoolStakingAddress", + "name": "_poolStakingAddress", "type": "address" - }, - { - "name": "_amount", - "type": "uint256" } ], - "name": "stake", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_fromPoolStakingAddress", - "type": "address" - }, + "name": "stakeAmountTotal", + "outputs": [ { - "name": "_amount", + "name": "", "type": "uint256" } ], - "name": "withdraw", - "outputs": [], "payable": false, - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { - "constant": false, + "constant": true, "inputs": [ { "name": "_poolStakingAddress", "type": "address" }, { - "name": "_amount", - "type": "int256" - } - ], - "name": "orderWithdraw", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_poolStakingAddress", + "name": "_staker", "type": "address" } ], - "name": "claimOrderedWithdraw", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ + "name": "stakeAmountMinusOrderedWithdraw", + "outputs": [ { - "name": "_erc20TokenContract", - "type": "address" + "name": "", + "type": "uint256" } ], - "name": "setErc20TokenContract", - "outputs": [], "payable": false, - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { - "constant": false, + "constant": true, "inputs": [ { - "name": "_minStake", - "type": "uint256" + "name": "_stakingAddress", + "type": "address" } ], - "name": "setCandidateMinStake", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ + "name": "poolInactiveIndex", + "outputs": [ { - "name": "_minStake", + "name": "", "type": "uint256" } ], - "name": "setDelegatorMinStake", - "outputs": [], "payable": false, - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { @@ -375,12 +205,21 @@ }, { "constant": true, - "inputs": [], - "name": "getPoolsInactive", + "inputs": [ + { + "name": "_poolStakingAddress", + "type": "address" + }, + { + "name": "_staker", + "type": "address" + } + ], + "name": "maxWithdrawAllowed", "outputs": [ { "name": "", - "type": "address[]" + "type": "uint256" } ], "payable": false, @@ -389,30 +228,21 @@ }, { "constant": true, - "inputs": [], - "name": "getPoolsLikelihood", - "outputs": [ + "inputs": [ { - "name": "likelihoods", - "type": "int256[]" + "name": "_poolStakingAddress", + "type": "address" }, { - "name": "sum", - "type": "int256" + "name": "_staker", + "type": "address" } ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getPoolsToBeElected", + "name": "stakeAmountByCurrentEpoch", "outputs": [ { "name": "", - "type": "address[]" + "type": "uint256" } ], "payable": false, @@ -422,11 +252,11 @@ { "constant": true, "inputs": [], - "name": "getPoolsToBeRemoved", + "name": "stakingEpoch", "outputs": [ { "name": "", - "type": "address[]" + "type": "uint256" } ], "payable": false, @@ -436,11 +266,11 @@ { "constant": true, "inputs": [], - "name": "areStakeAndWithdrawAllowed", + "name": "getDelegatorMinStake", "outputs": [ { "name": "", - "type": "bool" + "type": "uint256" } ], "payable": false, @@ -477,8 +307,17 @@ }, { "constant": true, - "inputs": [], - "name": "getDelegatorMinStake", + "inputs": [ + { + "name": "_poolStakingAddress", + "type": "address" + }, + { + "name": "_staker", + "type": "address" + } + ], + "name": "maxWithdrawOrderAllowed", "outputs": [ { "name": "", @@ -493,15 +332,19 @@ "constant": true, "inputs": [ { - "name": "_stakingAddress", + "name": "_poolStakingAddress", + "type": "address" + }, + { + "name": "_delegator", "type": "address" } ], - "name": "isPoolActive", + "name": "poolDelegatorInactiveIndex", "outputs": [ { "name": "", - "type": "bool" + "type": "uint256" } ], "payable": false, @@ -510,21 +353,49 @@ }, { "constant": true, + "inputs": [], + "name": "getPoolsLikelihood", + "outputs": [ + { + "name": "likelihoods", + "type": "int256[]" + }, + { + "name": "sum", + "type": "int256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, "inputs": [ { - "name": "_poolStakingAddress", + "name": "_unremovableStakingAddress", "type": "address" - }, + } + ], + "name": "clearUnremovableValidator", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ { - "name": "_staker", + "name": "_poolStakingAddress", "type": "address" } ], - "name": "maxWithdrawAllowed", + "name": "poolDelegators", "outputs": [ { "name": "", - "type": "uint256" + "type": "address[]" } ], "payable": false, @@ -543,7 +414,7 @@ "type": "address" } ], - "name": "maxWithdrawOrderAllowed", + "name": "orderWithdrawEpoch", "outputs": [ { "name": "", @@ -581,6 +452,20 @@ "stateMutability": "pure", "type": "function" }, + { + "constant": true, + "inputs": [], + "name": "getPoolsToBeElected", + "outputs": [ + { + "name": "", + "type": "address[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, { "constant": true, "inputs": [ @@ -593,7 +478,7 @@ "type": "address" } ], - "name": "orderedWithdrawAmount", + "name": "stakeAmount", "outputs": [ { "name": "", @@ -608,34 +493,80 @@ "constant": true, "inputs": [ { - "name": "_poolStakingAddress", + "name": "_stakingAddress", "type": "address" } ], - "name": "orderedWithdrawAmountTotal", + "name": "isPoolActive", "outputs": [ { "name": "", - "type": "uint256" + "type": "bool" } ], "payable": false, "stateMutability": "view", "type": "function" }, + { + "constant": false, + "inputs": [ + { + "name": "_toPoolStakingAddress", + "type": "address" + }, + { + "name": "_amount", + "type": "uint256" + } + ], + "name": "stake", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, { "constant": true, + "inputs": [ + { + "name": "_stakingAddress", + "type": "address" + } + ], + "name": "poolIndex", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, "inputs": [ { "name": "_poolStakingAddress", "type": "address" }, { - "name": "_staker", - "type": "address" + "name": "_amount", + "type": "int256" } ], - "name": "orderWithdrawEpoch", + "name": "orderWithdraw", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "STAKE_UNIT", "outputs": [ { "name": "", @@ -646,6 +577,34 @@ "stateMutability": "view", "type": "function" }, + { + "constant": false, + "inputs": [ + { + "name": "_poolStakingAddress", + "type": "address" + } + ], + "name": "claimOrderedWithdraw", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getPoolsToBeRemoved", + "outputs": [ + { + "name": "", + "type": "address[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, { "constant": true, "inputs": [ @@ -654,7 +613,7 @@ "type": "address" } ], - "name": "stakeAmountTotal", + "name": "orderedWithdrawAmountTotal", "outputs": [ { "name": "", @@ -665,19 +624,51 @@ "stateMutability": "view", "type": "function" }, + { + "constant": true, + "inputs": [], + "name": "getPoolsInactive", + "outputs": [ + { + "name": "", + "type": "address[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "validatorSetContract", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, { "constant": true, "inputs": [ { "name": "_poolStakingAddress", "type": "address" + }, + { + "name": "_staker", + "type": "address" } ], - "name": "poolDelegators", + "name": "orderedWithdrawAmount", "outputs": [ { "name": "", - "type": "address[]" + "type": "uint256" } ], "payable": false, @@ -686,17 +677,8 @@ }, { "constant": true, - "inputs": [ - { - "name": "_poolStakingAddress", - "type": "address" - }, - { - "name": "_delegator", - "type": "address" - } - ], - "name": "poolDelegatorIndex", + "inputs": [], + "name": "MAX_DELEGATORS_PER_POOL", "outputs": [ { "name": "", @@ -711,15 +693,11 @@ "constant": true, "inputs": [ { - "name": "_poolStakingAddress", - "type": "address" - }, - { - "name": "_delegator", + "name": "_stakingAddress", "type": "address" } ], - "name": "poolDelegatorInactiveIndex", + "name": "poolToBeRemovedIndex", "outputs": [ { "name": "", @@ -730,15 +708,19 @@ "stateMutability": "view", "type": "function" }, + { + "constant": false, + "inputs": [], + "name": "incrementStakingEpoch", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, { "constant": true, - "inputs": [ - { - "name": "_stakingAddress", - "type": "address" - } - ], - "name": "poolIndex", + "inputs": [], + "name": "MAX_CANDIDATES", "outputs": [ { "name": "", @@ -750,22 +732,21 @@ "type": "function" }, { - "constant": true, + "constant": false, "inputs": [ { - "name": "_stakingAddress", + "name": "_fromPoolStakingAddress", "type": "address" - } - ], - "name": "poolInactiveIndex", - "outputs": [ + }, { - "name": "", + "name": "_amount", "type": "uint256" } ], + "name": "withdraw", + "outputs": [], "payable": false, - "stateMutability": "view", + "stateMutability": "nonpayable", "type": "function" }, { @@ -788,87 +769,223 @@ "type": "function" }, { - "constant": true, + "anonymous": false, "inputs": [ { - "name": "_stakingAddress", + "indexed": true, + "name": "fromPoolStakingAddress", + "type": "address" + }, + { + "indexed": true, + "name": "staker", "type": "address" + }, + { + "indexed": true, + "name": "stakingEpoch", + "type": "uint256" + }, + { + "indexed": false, + "name": "amount", + "type": "uint256" } ], - "name": "poolToBeRemovedIndex", - "outputs": [ + "name": "Claimed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ { - "name": "", + "indexed": true, + "name": "toPoolStakingAddress", + "type": "address" + }, + { + "indexed": true, + "name": "staker", + "type": "address" + }, + { + "indexed": true, + "name": "stakingEpoch", + "type": "uint256" + }, + { + "indexed": false, + "name": "amount", "type": "uint256" } ], - "payable": false, - "stateMutability": "view", - "type": "function" + "name": "Staked", + "type": "event" }, { - "constant": true, + "anonymous": false, "inputs": [ { - "name": "_poolStakingAddress", + "indexed": false, + "name": "fromPoolStakingAddress", "type": "address" }, { - "name": "_staker", + "indexed": true, + "name": "toPoolStakingAddress", + "type": "address" + }, + { + "indexed": true, + "name": "staker", "type": "address" + }, + { + "indexed": true, + "name": "stakingEpoch", + "type": "uint256" + }, + { + "indexed": false, + "name": "amount", + "type": "uint256" } ], - "name": "stakeAmount", - "outputs": [ + "name": "StakeMoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ { - "name": "", + "indexed": true, + "name": "fromPoolStakingAddress", + "type": "address" + }, + { + "indexed": true, + "name": "staker", + "type": "address" + }, + { + "indexed": true, + "name": "stakingEpoch", "type": "uint256" + }, + { + "indexed": false, + "name": "amount", + "type": "int256" } ], - "payable": false, - "stateMutability": "view", - "type": "function" + "name": "WithdrawalOrdered", + "type": "event" }, { - "constant": true, + "anonymous": false, "inputs": [ { - "name": "_poolStakingAddress", + "indexed": true, + "name": "fromPoolStakingAddress", "type": "address" }, { - "name": "_staker", + "indexed": true, + "name": "staker", "type": "address" + }, + { + "indexed": true, + "name": "stakingEpoch", + "type": "uint256" + }, + { + "indexed": false, + "name": "amount", + "type": "uint256" } ], - "name": "stakeAmountByCurrentEpoch", - "outputs": [ + "name": "Withdrawn", + "type": "event" + }, + { + "constant": false, + "inputs": [ { - "name": "", + "name": "_amount", "type": "uint256" + }, + { + "name": "_miningAddress", + "type": "address" } ], + "name": "addPool", + "outputs": [], "payable": false, - "stateMutability": "view", + "stateMutability": "nonpayable", "type": "function" }, { - "constant": true, + "constant": false, "inputs": [ { - "name": "_poolStakingAddress", + "name": "_validatorSetContract", "type": "address" }, { - "name": "_staker", + "name": "_erc20TokenContract", "type": "address" + }, + { + "name": "_initialStakingAddresses", + "type": "address[]" + }, + { + "name": "_delegatorMinStake", + "type": "uint256" + }, + { + "name": "_candidateMinStake", + "type": "uint256" + }, + { + "name": "_stakingEpochDuration", + "type": "uint256" + }, + { + "name": "_stakeWithdrawDisallowPeriod", + "type": "uint256" } ], - "name": "stakeAmountMinusOrderedWithdraw", + "name": "initialize", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_blockNumber", + "type": "uint256" + } + ], + "name": "setStakingEpochStartBlock", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "areStakeAndWithdrawAllowed", "outputs": [ { "name": "", - "type": "uint256" + "type": "bool" } ], "payable": false, @@ -877,13 +994,22 @@ }, { "constant": true, - "inputs": [ + "inputs": [], + "name": "stakeWithdrawDisallowPeriod", + "outputs": [ { - "name": "_poolStakingAddress", - "type": "address" + "name": "", + "type": "uint256" } ], - "name": "stakeAmountTotalMinusOrderedWithdraw", + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "stakingEpochDuration", "outputs": [ { "name": "", @@ -897,7 +1023,7 @@ { "constant": true, "inputs": [], - "name": "stakingEpoch", + "name": "stakingEpochStartBlock", "outputs": [ { "name": "", @@ -911,11 +1037,11 @@ { "constant": true, "inputs": [], - "name": "validatorSetContract", + "name": "stakingEpochEndBlock", "outputs": [ { "name": "", - "type": "address" + "type": "uint256" } ], "payable": false, From f587ecc6dd9610b7e6a3e3ce7448ede1e2d99f5a Mon Sep 17 00:00:00 2001 From: saneery Date: Wed, 8 May 2019 15:08:15 +0300 Subject: [PATCH 05/68] remove unwanted changes --- apps/explorer/lib/explorer/application.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index d886dd8a0b..d609205d41 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -29,8 +29,7 @@ defmodule Explorer.Application do Explorer.SmartContract.SolcDownloader, {Registry, keys: :duplicate, name: Registry.ChainEvents, id: Registry.ChainEvents}, {Admin.Recovery, [[], [name: Admin.Recovery]]}, - {TransactionCountCache, [[], []]}, - {BlockCountCache, []} + {TransactionCountCache, [[], []]} ] children = base_children ++ configurable_children() @@ -40,6 +39,7 @@ defmodule Explorer.Application do res = Supervisor.start_link(children, opts) BlockNumberCache.setup() + BlockCountCache.setup() res end From 632fb29d1ec29988d718f20b532407fa821ef488 Mon Sep 17 00:00:00 2001 From: saneery Date: Wed, 8 May 2019 16:00:34 +0300 Subject: [PATCH 06/68] add epoch counter test --- .../lib/explorer/staking/epoch_counter.ex | 30 +++--- .../explorer/staking/epoch_counter_test.exs | 97 +++++++++++++++++++ 2 files changed, 114 insertions(+), 13 deletions(-) create mode 100644 apps/explorer/test/explorer/staking/epoch_counter_test.exs diff --git a/apps/explorer/lib/explorer/staking/epoch_counter.ex b/apps/explorer/lib/explorer/staking/epoch_counter.ex index 256bc6fe0b..ecaf3e3307 100644 --- a/apps/explorer/lib/explorer/staking/epoch_counter.ex +++ b/apps/explorer/lib/explorer/staking/epoch_counter.ex @@ -1,6 +1,6 @@ defmodule Explorer.Staking.EpochCounter do @moduledoc """ - Counts staking epoch + Fetches current staking epoch number and the epoch end block number """ use GenServer @@ -14,23 +14,27 @@ defmodule Explorer.Staking.EpochCounter do @doc "Current staking epoch number" def epoch_number do - case :ets.lookup(@table_name, @epoch_key) do - [{_, epoch_num}] -> - epoch_num - - _ -> - 0 + if :ets.info(@table_name) != :undefined do + case :ets.lookup(@table_name, @epoch_key) do + [{_, epoch_num}] -> + epoch_num + + _ -> + 0 + end end end @doc "Block number on which will start new epoch" def epoch_end_block do - case :ets.lookup(@table_name, @epoch_end_key) do - [{_, epoch_end}] -> - epoch_end - - _ -> - 0 + if :ets.info(@table_name) != :undefined do + case :ets.lookup(@table_name, @epoch_end_key) do + [{_, epoch_end}] -> + epoch_end + + _ -> + 0 + end end end diff --git a/apps/explorer/test/explorer/staking/epoch_counter_test.exs b/apps/explorer/test/explorer/staking/epoch_counter_test.exs new file mode 100644 index 0000000000..b280f28aa4 --- /dev/null +++ b/apps/explorer/test/explorer/staking/epoch_counter_test.exs @@ -0,0 +1,97 @@ +defmodule Explorer.Staking.EpochCounterTest do + use ExUnit.Case, async: false + + import Mox + + alias Explorer.Staking.EpochCounter + alias Explorer.Chain.Events.Publisher + + setup :verify_on_exit! + setup :set_mox_global + + test "when disabled, it returns nil" do + assert EpochCounter.epoch_number() == nil + assert EpochCounter.epoch_end_block() == nil + end + + test "fetch epoch data" do + set_mox(10, 880) + Application.put_env(:explorer, EpochCounter, enabled: true) + start_supervised!(EpochCounter) + + Process.sleep(1_000) + + assert EpochCounter.epoch_number() == 10 + assert EpochCounter.epoch_end_block() == 880 + end + + test "fetch new epoch data" do + set_mox(10, 880) + Application.put_env(:explorer, EpochCounter, enabled: true) + start_supervised!(EpochCounter) + + Process.sleep(1_000) + + assert EpochCounter.epoch_number() == 10 + assert EpochCounter.epoch_end_block() == 880 + + event_type = :blocks + broadcast_type = :realtime + event_data = [%{number: 881}] + + set_mox(11, 960) + Publisher.broadcast([{event_type, event_data}], broadcast_type) + + Process.sleep(1_000) + + assert EpochCounter.epoch_number() == 11 + assert EpochCounter.epoch_end_block() == 960 + end + + defp set_mox(epoch_num, end_block_num) do + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [ + %{ + id: 0, + jsonrpc: "2.0", + method: "eth_call", + params: _ + }, + %{ + id: 1, + jsonrpc: "2.0", + method: "eth_call", + params: _ + } + ], _options -> + {:ok, + [ + %{ + id: 0, + jsonrpc: "2.0", + result: encode_num(epoch_num) + }, + %{ + id: 1, + jsonrpc: "2.0", + result: encode_num(end_block_num) + } + ] + } + end + ) + end + + defp encode_num(num) do + selector = %ABI.FunctionSelector{function: nil, types: [uint: 32]} + + encoded_num = + [num] + |> ABI.TypeEncoder.encode(selector) + |> Base.encode16(case: :lower) + + "0x" <> encoded_num + end +end From 7833654c162557adcdf1253af798027d5a4c237d Mon Sep 17 00:00:00 2001 From: saneery Date: Wed, 8 May 2019 16:12:51 +0300 Subject: [PATCH 07/68] update pools reader test --- .../explorer/staking/pools_reader_test.exs | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/apps/explorer/test/explorer/staking/pools_reader_test.exs b/apps/explorer/test/explorer/staking/pools_reader_test.exs index ac6a600722..1795f247b2 100644 --- a/apps/explorer/test/explorer/staking/pools_reader_test.exs +++ b/apps/explorer/test/explorer/staking/pools_reader_test.exs @@ -1,6 +1,5 @@ defmodule Explorer.Token.PoolsReaderTest do use EthereumJSONRPC.Case - use Explorer.DataCase alias Explorer.Staking.PoolsReader @@ -44,6 +43,7 @@ defmodule Explorer.Token.PoolsReaderTest do mining_address: <<187, 202, 168, 212, 130, 137, 187, 31, 252, 249, 128, 141, 154, 164, 177, 210, 21, 5, 76, 120>>, staked_amount: 0, + self_staked_amount: 0, staking_address: <<11, 47, 94, 47, 60, 189, 134, 78, 170, 44, 100, 46, 55, 105, 193, 88, 35, 97, 202, 246>>, was_banned_count: 0, was_validator_count: 2 @@ -162,6 +162,24 @@ defmodule Explorer.Token.PoolsReaderTest do result: "0x0000000000000000000000000000000000000000000000000000000000000000" } + # stakeAmountMinusOrderedWithdraw + %{ + id: id, + jsonrpc: "2.0", + method: "eth_call", + params: [ + %{ + data: "0x58daab6a0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf60000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", + to: _ + }, + "latest" + ] + } -> + %{ + id: id, + result: "0x0000000000000000000000000000000000000000000000000000000000000000" + } + # isValidator %{ id: id, From f370e885bd06bd68a515c98bdfad5737a3004eed Mon Sep 17 00:00:00 2001 From: saneery Date: Wed, 8 May 2019 16:16:21 +0300 Subject: [PATCH 08/68] mix format --- .../lib/explorer/staking/pools_reader.ex | 2 +- .../explorer/staking/epoch_counter_test.exs | 52 +++++++++---------- .../explorer/staking/pools_reader_test.exs | 31 +++++------ 3 files changed, 43 insertions(+), 42 deletions(-) diff --git a/apps/explorer/lib/explorer/staking/pools_reader.ex b/apps/explorer/lib/explorer/staking/pools_reader.ex index e3bb27084f..608fea3863 100644 --- a/apps/explorer/lib/explorer/staking/pools_reader.ex +++ b/apps/explorer/lib/explorer/staking/pools_reader.ex @@ -87,7 +87,7 @@ defmodule Explorer.Staking.PoolsReader do {:validators, "validatorCounter", [mining_address]}, {:validators, "isValidatorBanned", [mining_address]}, {:validators, "bannedUntil", [mining_address]}, - {:validators, "banCounter", [mining_address]}, + {:validators, "banCounter", [mining_address]} ] methods diff --git a/apps/explorer/test/explorer/staking/epoch_counter_test.exs b/apps/explorer/test/explorer/staking/epoch_counter_test.exs index b280f28aa4..5232a71edb 100644 --- a/apps/explorer/test/explorer/staking/epoch_counter_test.exs +++ b/apps/explorer/test/explorer/staking/epoch_counter_test.exs @@ -53,33 +53,33 @@ defmodule Explorer.Staking.EpochCounterTest do EthereumJSONRPC.Mox, :json_rpc, fn [ - %{ - id: 0, - jsonrpc: "2.0", - method: "eth_call", - params: _ - }, - %{ - id: 1, - jsonrpc: "2.0", - method: "eth_call", - params: _ - } - ], _options -> + %{ + id: 0, + jsonrpc: "2.0", + method: "eth_call", + params: _ + }, + %{ + id: 1, + jsonrpc: "2.0", + method: "eth_call", + params: _ + } + ], + _options -> {:ok, - [ - %{ - id: 0, - jsonrpc: "2.0", - result: encode_num(epoch_num) - }, - %{ - id: 1, - jsonrpc: "2.0", - result: encode_num(end_block_num) - } - ] - } + [ + %{ + id: 0, + jsonrpc: "2.0", + result: encode_num(epoch_num) + }, + %{ + id: 1, + jsonrpc: "2.0", + result: encode_num(end_block_num) + } + ]} end ) end diff --git a/apps/explorer/test/explorer/staking/pools_reader_test.exs b/apps/explorer/test/explorer/staking/pools_reader_test.exs index 1795f247b2..bb3af9fbcc 100644 --- a/apps/explorer/test/explorer/staking/pools_reader_test.exs +++ b/apps/explorer/test/explorer/staking/pools_reader_test.exs @@ -164,21 +164,22 @@ defmodule Explorer.Token.PoolsReaderTest do # stakeAmountMinusOrderedWithdraw %{ - id: id, - jsonrpc: "2.0", - method: "eth_call", - params: [ - %{ - data: "0x58daab6a0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf60000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", - to: _ - }, - "latest" - ] - } -> - %{ - id: id, - result: "0x0000000000000000000000000000000000000000000000000000000000000000" - } + id: id, + jsonrpc: "2.0", + method: "eth_call", + params: [ + %{ + data: + "0x58daab6a0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf60000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", + to: _ + }, + "latest" + ] + } -> + %{ + id: id, + result: "0x0000000000000000000000000000000000000000000000000000000000000000" + } # isValidator %{ From 5e66eda0425bc80f5df7529bf5b23156f6376521 Mon Sep 17 00:00:00 2001 From: saneery Date: Wed, 8 May 2019 18:02:47 +0300 Subject: [PATCH 09/68] test getting staking pools --- apps/explorer/lib/explorer/chain.ex | 69 +++++++++++++--------- apps/explorer/test/explorer/chain_test.exs | 49 +++++++++++++++ 2 files changed, 89 insertions(+), 29 deletions(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 840b2fb288..b2964c85b0 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -2832,13 +2832,27 @@ defmodule Explorer.Chain do value end - @doc """ - List of staking pools which are validators - """ - @spec validators_pools(lim :: integer, off :: integer) :: [map] - def validators_pools(lim, off) when is_integer(lim) and is_integer(off) do + @doc "Get staking pools from the DB" + @spec staking_pools(filter :: :validator | :active | :inactive, lim :: integer, off :: integer) :: [map()] + def staking_pools(filter, lim, off) when is_integer(lim) and is_integer(off) do Address.Name - |> where( + |> staking_pool_filter(filter) + |> limit(^lim) + |> offset(^off) + |> Repo.all() + end + + @doc "Get count of staking pools from the DB" + @spec staking_pools_count(filter :: :validator | :active | :inactive) :: integer + def staking_pools_count(filter) do + Address.Name + |> staking_pool_filter(filter) + |> Repo.aggregate(:count, :address_hash) + end + + defp staking_pool_filter(query, :validator) do + where( + query, [_], fragment(""" (metadata->>'is_active')::boolean = true and @@ -2846,35 +2860,32 @@ defmodule Explorer.Chain do (metadata->>'is_validator')::boolean = true """) ) - |> limit(^lim) - |> offset(^off) - |> Repo.all() end - @doc """ - List of active pools - """ - @spec active_pools(lim :: integer, off :: integer) :: [map] - def active_pools(lim, off) when is_integer(lim) and is_integer(off) do - Address.Name - |> where([_], fragment("(metadata->>'is_active')::boolean = true")) - |> limit(^lim) - |> offset(^off) - |> Repo.all() + defp staking_pool_filter(query, :active) do + where( + query, + [_], + fragment(""" + (metadata->>'is_active')::boolean = true and + (metadata->>'deleted')::boolean is not true + """) + ) end - @doc """ - List of inactive pools - """ - @spec inactive_pools(lim :: integer, off :: integer) :: [map] - def inactive_pools(lim, off) when is_integer(lim) and is_integer(off) do - Address.Name - |> where([_], fragment("(metadata->>'is_active')::boolean = false")) - |> limit(^lim) - |> offset(^off) - |> Repo.all() + defp staking_pool_filter(query, :inactive) do + where( + query, + [_], + fragment(""" + (metadata->>'is_active')::boolean = false and + (metadata->>'deleted')::boolean is not true + """) + ) end + defp staking_pool_filter(query, _), do: query + defp with_decompiled_code_flag(query, hash) do has_decompiled_code_query = from(decompiled_contract in DecompiledSmartContract, diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index a9b479c3bd..ec4fe63196 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -3903,4 +3903,53 @@ defmodule Explorer.ChainTest do refute Chain.contract_address?(to_string(hash), 1, json_rpc_named_arguments) end end + + describe "staking_pools/3" do + test "validators staking pools" do + inserted_validator = insert(:address_name, primary: true, metadata: %{is_active: true, is_validator: true}) + insert(:address_name, primary: true, metadata: %{is_active: true, is_validator: false}) + + assert [gotten_validator] = Chain.staking_pools(:validator, 20, 0) + assert inserted_validator.address_hash == gotten_validator.address_hash + end + + test "active staking pools" do + inserted_validator = insert(:address_name, primary: true, metadata: %{is_active: true}) + insert(:address_name, primary: true, metadata: %{is_active: false}) + + assert [gotten_validator] = Chain.staking_pools(:active, 20, 0) + assert inserted_validator.address_hash == gotten_validator.address_hash + end + + test "inactive staking pools" do + insert(:address_name, primary: true, metadata: %{is_active: true}) + inserted_validator = insert(:address_name, primary: true, metadata: %{is_active: false}) + + assert [gotten_validator] = Chain.staking_pools(:inactive, 20, 0) + assert inserted_validator.address_hash == gotten_validator.address_hash + end + end + + describe "staking_pools_count/1" do + test "validators staking pools" do + insert(:address_name, primary: true, metadata: %{is_active: true, is_validator: true}) + insert(:address_name, primary: true, metadata: %{is_active: true, is_validator: false}) + + assert Chain.staking_pools_count(:validator) == 1 + end + + test "active staking pools" do + insert(:address_name, primary: true, metadata: %{is_active: true}) + insert(:address_name, primary: true, metadata: %{is_active: false}) + + assert Chain.staking_pools_count(:active) == 1 + end + + test "inactive staking pools" do + insert(:address_name, primary: true, metadata: %{is_active: true}) + insert(:address_name, primary: true, metadata: %{is_active: false}) + + assert Chain.staking_pools_count(:inactive) == 1 + end + end end From 57f924f440e4c040c8691b527d956119120c1f43 Mon Sep 17 00:00:00 2001 From: saneery Date: Wed, 8 May 2019 18:18:08 +0300 Subject: [PATCH 10/68] edit config --- apps/explorer/config/config.exs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index 529f4698cf..7e7800524d 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -58,9 +58,13 @@ config :explorer, Explorer.Staking.PoolsReader, validators_contract_address: System.get_env("POS_VALIDATORS_CONTRACT"), staking_contract_address: System.get_env("POS_STAKING_CONTRACT") -config :explorer, Explorer.Staking.EpochCounter, - enabled: false, - staking_contract_address: System.get_env("POS_STAKING_CONTRACT") +if System.get_env("POS_STAKING_CONTRACT") do + config :explorer, Explorer.Staking.EpochCounter, + enabled: true, + staking_contract_address: System.get_env("POS_STAKING_CONTRACT") +else + config :explorer, Explorer.Staking.EpochCounter, enabled: false +end if System.get_env("SUPPLY_MODULE") == "TokenBridge" do config :explorer, supply: Explorer.Chain.Supply.TokenBridge From 147f0c43474cd4e3e088225320f6bdd882c0391f Mon Sep 17 00:00:00 2001 From: saneery Date: Wed, 8 May 2019 18:41:21 +0300 Subject: [PATCH 11/68] edit staking pools fetcher test --- .../indexer/fetcher/staking_pools_test.exs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/apps/indexer/test/indexer/fetcher/staking_pools_test.exs b/apps/indexer/test/indexer/fetcher/staking_pools_test.exs index 8f985537bf..13e2c0d7ee 100644 --- a/apps/indexer/test/indexer/fetcher/staking_pools_test.exs +++ b/apps/indexer/test/indexer/fetcher/staking_pools_test.exs @@ -129,6 +129,25 @@ defmodule Indexer.Fetcher.StakingPoolsTest do result: "0x0000000000000000000000000000000000000000000000000000000000000000" } + # stakeAmountMinusOrderedWithdraw + %{ + id: id, + jsonrpc: "2.0", + method: "eth_call", + params: [ + %{ + data: + "0x58daab6a0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf60000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", + to: _ + }, + "latest" + ] + } -> + %{ + id: id, + result: "0x0000000000000000000000000000000000000000000000000000000000000000" + } + # isValidator %{ id: id, From 2f4d569acd9d90d14176186323c1b1d79b9f11a0 Mon Sep 17 00:00:00 2001 From: saneery Date: Wed, 8 May 2019 18:58:11 +0300 Subject: [PATCH 12/68] additional changes --- apps/explorer/lib/explorer/staking/epoch_counter.ex | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/explorer/lib/explorer/staking/epoch_counter.ex b/apps/explorer/lib/explorer/staking/epoch_counter.ex index ecaf3e3307..9b50d983de 100644 --- a/apps/explorer/lib/explorer/staking/epoch_counter.ex +++ b/apps/explorer/lib/explorer/staking/epoch_counter.ex @@ -62,14 +62,16 @@ defmodule Explorer.Staking.EpochCounter do end def handle_info({:chain_event, :blocks, :realtime, blocks}, state) do - new_block = Enum.max_by(blocks, &Map.get(&1, :number), fn -> 0 end) - block_number = new_block.number + new_block_number = + blocks + |> Enum.map(& &1[:number]) + |> Enum.max(fn -> 0 end) case :ets.lookup(@table_name, @epoch_end_key) do [] -> fetch_epoch_info() - [{_, epoch_end_block}] when epoch_end_block < block_number -> + [{_, epoch_end_block}] when epoch_end_block < new_block_number -> fetch_epoch_info() _ -> From 42b210934988a4240d4f292d6c3df8e2de8e0112 Mon Sep 17 00:00:00 2001 From: saneery Date: Thu, 9 May 2019 10:44:05 +0300 Subject: [PATCH 13/68] module doc --- apps/explorer/lib/explorer/staking/epoch_counter.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/staking/epoch_counter.ex b/apps/explorer/lib/explorer/staking/epoch_counter.ex index 9b50d983de..2ada66bf65 100644 --- a/apps/explorer/lib/explorer/staking/epoch_counter.ex +++ b/apps/explorer/lib/explorer/staking/epoch_counter.ex @@ -1,6 +1,7 @@ defmodule Explorer.Staking.EpochCounter do @moduledoc """ - Fetches current staking epoch number and the epoch end block number + Fetches current staking epoch number and the epoch end block number. + It subscribes to handle new blocks and conclude whether the epoch is over. """ use GenServer From 7680df043946af0838ce3e270317e6d17ec6b198 Mon Sep 17 00:00:00 2001 From: saneery Date: Thu, 9 May 2019 10:49:20 +0300 Subject: [PATCH 14/68] function doc --- apps/explorer/lib/explorer/staking/epoch_counter.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/explorer/lib/explorer/staking/epoch_counter.ex b/apps/explorer/lib/explorer/staking/epoch_counter.ex index 2ada66bf65..a53425ce9c 100644 --- a/apps/explorer/lib/explorer/staking/epoch_counter.ex +++ b/apps/explorer/lib/explorer/staking/epoch_counter.ex @@ -62,6 +62,7 @@ defmodule Explorer.Staking.EpochCounter do {:noreply, state} end + @doc "Handles new blocks and decides to fetch new epoch info" def handle_info({:chain_event, :blocks, :realtime, blocks}, state) do new_block_number = blocks From 8bbdb17265b3b2cf7c88ddb5234161683f880cdb Mon Sep 17 00:00:00 2001 From: saneery Date: Mon, 13 May 2019 11:53:02 +0300 Subject: [PATCH 15/68] use ecto fragment interpolation --- apps/explorer/lib/explorer/chain.ex | 45 ++++++++++++------- .../chain/import/runner/staking_pools.ex | 4 +- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index b2964c85b0..33992a41d1 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -2853,34 +2853,47 @@ defmodule Explorer.Chain do defp staking_pool_filter(query, :validator) do where( query, - [_], - fragment(""" - (metadata->>'is_active')::boolean = true and - (metadata->>'deleted')::boolean is not true and - (metadata->>'is_validator')::boolean = true - """) + [address], + fragment( + """ + (?->>'is_active')::boolean = true and + (?->>'deleted')::boolean is not true and + (?->>'is_validator')::boolean = true + """, + address.metadata, + address.metadata, + address.metadata + ) ) end defp staking_pool_filter(query, :active) do where( query, - [_], - fragment(""" - (metadata->>'is_active')::boolean = true and - (metadata->>'deleted')::boolean is not true - """) + [address], + fragment( + """ + (?->>'is_active')::boolean = true and + (?->>'deleted')::boolean is not true + """, + address.metadata, + address.metadata + ) ) end defp staking_pool_filter(query, :inactive) do where( query, - [_], - fragment(""" - (metadata->>'is_active')::boolean = false and - (metadata->>'deleted')::boolean is not true - """) + [address], + fragment( + """ + (?->>'is_active')::boolean = false and + (?->>'deleted')::boolean is not true + """, + address.metadata, + address.metadata + ) ) end diff --git a/apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex b/apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex index f0e62b7e89..c7736efad9 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex @@ -60,10 +60,10 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do address_name in Address.Name, where: address_name.address_hash not in ^addresses and - fragment("(metadata->>'is_pool')::boolean = true"), + fragment("(?->>'is_pool')::boolean = true", address_name.metadata), update: [ set: [ - metadata: fragment("metadata || '{\"deleted\": true}'::jsonb") + metadata: fragment("? || '{\"deleted\": true}'::jsonb", address_name.metadata) ] ] ) From f46970cab048141e4dbba0032b1d05c4fcb2b336 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 13 May 2019 13:21:05 +0300 Subject: [PATCH 16/68] add eth_BlockNumber --- .../controllers/api/rpc/block_controller.ex | 6 ++++++ .../block_scout_web/views/api/rpc/block_view.ex | 8 ++++++++ apps/explorer/lib/explorer/chain.ex | 16 ++++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex index e6991bf1af..a9845a2dc2 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex @@ -23,4 +23,10 @@ defmodule BlockScoutWeb.API.RPC.BlockController do render(conn, :error, error: "Block does not exist") end end + + def eth_block_number(conn, _params) do + max_block_number = Chain.fetch_max_block_number() + + render(conn, :eth_block_number, number: max_block_number) + end end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/block_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/block_view.ex index 941a064804..1f1374adec 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/block_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/block_view.ex @@ -22,6 +22,14 @@ defmodule BlockScoutWeb.API.RPC.BlockView do RPCView.render("show.json", data: data) end + def render("eth_block_number.json", %{number: number}) do + data = %{ + "blockNumber" => to_string(number) + } + + RPCView.render("show.json", data: data) + end + def render("error.json", %{error: error}) do RPCView.render("error.json", error: error) end diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 713d449d72..1f96200ae3 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -1059,6 +1059,22 @@ defmodule Explorer.Chain do end end + @spec fetch_max_block_number() :: non_neg_integer + def fetch_max_block_number do + query = + from(block in Block, + select: max(block.number), + where: block.consensus == true + ) + + result = Repo.one!(query) + + case result do + nil -> 0 + _ -> result + end + end + @spec fetch_count_consensus_block() :: non_neg_integer def fetch_count_consensus_block do query = From 70b1a6dd35d32e10ce3ef0b314265c21960f5fde Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 13 May 2019 14:14:48 +0300 Subject: [PATCH 17/68] mimic eth response --- .../controllers/api/rpc/block_controller.ex | 5 +++-- .../lib/block_scout_web/views/api/rpc/block_view.ex | 12 ++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex index a9845a2dc2..31f3ad3d3d 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex @@ -24,9 +24,10 @@ defmodule BlockScoutWeb.API.RPC.BlockController do end end - def eth_block_number(conn, _params) do + def eth_block_number(conn, params) do + id = Map.get(params, "id", 1) max_block_number = Chain.fetch_max_block_number() - render(conn, :eth_block_number, number: max_block_number) + render(conn, :eth_block_number, number: max_block_number, id: id) end end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/block_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/block_view.ex index 1f1374adec..9b3460af49 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/block_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/block_view.ex @@ -1,7 +1,7 @@ defmodule BlockScoutWeb.API.RPC.BlockView do use BlockScoutWeb, :view - alias BlockScoutWeb.API.RPC.RPCView + alias BlockScoutWeb.API.RPC.{EthRPCView, RPCView} alias Explorer.Chain.{Hash, Wei} def render("block_reward.json", %{block: block, reward: reward}) do @@ -22,15 +22,15 @@ defmodule BlockScoutWeb.API.RPC.BlockView do RPCView.render("show.json", data: data) end - def render("eth_block_number.json", %{number: number}) do - data = %{ - "blockNumber" => to_string(number) - } + def render("eth_block_number.json", %{number: number, id: id}) do + result = integer_to_hex(number) - RPCView.render("show.json", data: data) + EthRPCView.render("show.json", %{result: result, id: id}) end def render("error.json", %{error: error}) do RPCView.render("error.json", error: error) end + + defp integer_to_hex(integer), do: Integer.to_string(integer, 16) end From 93f647902eb76564f12be9bac571227d0e2508cd Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 13 May 2019 14:16:39 +0300 Subject: [PATCH 18/68] add rpc view --- .../views/api/rpc/eth_rpc_view.ex | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 apps/block_scout_web/lib/block_scout_web/views/api/rpc/eth_rpc_view.ex diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/eth_rpc_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/eth_rpc_view.ex new file mode 100644 index 0000000000..f82e476221 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/eth_rpc_view.ex @@ -0,0 +1,19 @@ +defmodule BlockScoutWeb.API.RPC.EthRPCView do + use BlockScoutWeb, :view + + def render("show.json", %{result: result, id: id}) do + %{ + "id" => id, + "jsonrpc" => "2.0", + "result" => result + } + end + + def render("error.json", %{error: message, id: id}) do + %{ + "id" => id, + "jsonrpc" => "2.0", + "error" => message + } + end +end From 4a2dd4760b02eebd32ccd564d567aada545812ae Mon Sep 17 00:00:00 2001 From: Gabriel Rodriguez Alsina Date: Mon, 13 May 2019 20:47:44 -0300 Subject: [PATCH 19/68] (fix) Wrong styles for tokens dropdown --- apps/block_scout_web/assets/css/components/_card.scss | 4 ++-- apps/block_scout_web/assets/css/components/_dropdown.scss | 4 ++-- .../templates/address_token_balance/_token_balances.html.eex | 4 ++-- .../templates/address_token_balance/_tokens.html.eex | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/assets/css/components/_card.scss b/apps/block_scout_web/assets/css/components/_card.scss index 2e2cba08a9..dbaac15664 100644 --- a/apps/block_scout_web/assets/css/components/_card.scss +++ b/apps/block_scout_web/assets/css/components/_card.scss @@ -18,8 +18,8 @@ $card-background-1-text-color: #fff !default; background-color: $card-background-1; color: $card-background-1-text-color; - a, - a:hover { + a:not(.dropdown-item), + a:not(.dropdown-item):hover { color: $card-background-1-text-color; } } diff --git a/apps/block_scout_web/assets/css/components/_dropdown.scss b/apps/block_scout_web/assets/css/components/_dropdown.scss index b603d53fea..c8962c021d 100644 --- a/apps/block_scout_web/assets/css/components/_dropdown.scss +++ b/apps/block_scout_web/assets/css/components/_dropdown.scss @@ -60,9 +60,9 @@ .dropdown-search-icon { color: $gray-300; - left: 0.625rem; + left: 8px; pointer-events: none; - top: 0.5rem; + top: 5px; } .dropdown-search-field { diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex index 6f1dc762fd..de0215727c 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex @@ -1,6 +1,6 @@ <%= if Enum.any?(@token_balances) do %> -
-

<%= token_name(token_balance.token) %>

+

xxxxxxxxxxxxx<%= token_name(token_balance.token) %>

<%= if token_balance.token.usd_value do %>

@@ -23,7 +23,7 @@ <% end %>

-

+

xxxxxxxxxxx <%= format_according_to_decimals(token_balance.value, token_balance.token.decimals) %> <%= token_balance.token.symbol %>

<%= if token_balance.token.usd_value do %> From 74f4e5d269500f41c2e7569498e781df8593ba42 Mon Sep 17 00:00:00 2001 From: Gabriel Rodriguez Alsina Date: Mon, 13 May 2019 20:54:01 -0300 Subject: [PATCH 20/68] (update) changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d4771720c..8e097264da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ ### Fixes +- [#1944](https://github.com/poanetwork/blockscout/pull/1944) - fixed styles for token's dropdown. - [#1926](https://github.com/poanetwork/blockscout/pull/1926) - status label alignment - [#1829](https://github.com/poanetwork/blockscout/pull/1829) - Handle nil quantities in block decoding routine - [#1830](https://github.com/poanetwork/blockscout/pull/1830) - Make block size field nullable From 8645e02e3c9ce62f55dde418679f355fde8f4934 Mon Sep 17 00:00:00 2001 From: saneery Date: Tue, 14 May 2019 13:47:19 +0300 Subject: [PATCH 21/68] use paging options struct in staking pools functions --- apps/explorer/lib/explorer/chain.ex | 8 +++++--- apps/explorer/lib/explorer/paging_options.ex | 5 +++-- apps/explorer/test/explorer/chain_test.exs | 12 +++++++++--- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 33992a41d1..fe1c6b1b5f 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -2833,11 +2833,13 @@ defmodule Explorer.Chain do end @doc "Get staking pools from the DB" - @spec staking_pools(filter :: :validator | :active | :inactive, lim :: integer, off :: integer) :: [map()] - def staking_pools(filter, lim, off) when is_integer(lim) and is_integer(off) do + @spec staking_pools(filter :: :validator | :active | :inactive, options :: PagingOptions.t()) :: [map()] + def staking_pools(filter, %PagingOptions{page_size: page_size, page_number: page_number} \\ @default_paging_options) do + off = page_size * (page_number - 1) + Address.Name |> staking_pool_filter(filter) - |> limit(^lim) + |> limit(^page_size) |> offset(^off) |> Repo.all() end diff --git a/apps/explorer/lib/explorer/paging_options.ex b/apps/explorer/lib/explorer/paging_options.ex index 1bac2dc71a..0828a0afad 100644 --- a/apps/explorer/lib/explorer/paging_options.ex +++ b/apps/explorer/lib/explorer/paging_options.ex @@ -4,10 +4,11 @@ defmodule Explorer.PagingOptions do number and index. """ - @type t :: %__MODULE__{key: key, page_size: page_size} + @type t :: %__MODULE__{key: key, page_size: page_size, page_number: page_number} @typep key :: any() @typep page_size :: non_neg_integer() + @typep page_number :: pos_integer() - defstruct [:key, :page_size] + defstruct [:key, :page_size, page_number: 1] end diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index ec4fe63196..24157c3938 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -3909,7 +3909,9 @@ defmodule Explorer.ChainTest do inserted_validator = insert(:address_name, primary: true, metadata: %{is_active: true, is_validator: true}) insert(:address_name, primary: true, metadata: %{is_active: true, is_validator: false}) - assert [gotten_validator] = Chain.staking_pools(:validator, 20, 0) + options = %PagingOptions{page_size: 20, page_number: 1} + + assert [gotten_validator] = Chain.staking_pools(:validator, options) assert inserted_validator.address_hash == gotten_validator.address_hash end @@ -3917,7 +3919,9 @@ defmodule Explorer.ChainTest do inserted_validator = insert(:address_name, primary: true, metadata: %{is_active: true}) insert(:address_name, primary: true, metadata: %{is_active: false}) - assert [gotten_validator] = Chain.staking_pools(:active, 20, 0) + options = %PagingOptions{page_size: 20, page_number: 1} + + assert [gotten_validator] = Chain.staking_pools(:active, options) assert inserted_validator.address_hash == gotten_validator.address_hash end @@ -3925,7 +3929,9 @@ defmodule Explorer.ChainTest do insert(:address_name, primary: true, metadata: %{is_active: true}) inserted_validator = insert(:address_name, primary: true, metadata: %{is_active: false}) - assert [gotten_validator] = Chain.staking_pools(:inactive, 20, 0) + options = %PagingOptions{page_size: 20, page_number: 1} + + assert [gotten_validator] = Chain.staking_pools(:inactive, options) assert inserted_validator.address_hash == gotten_validator.address_hash end end From dd711fa9395eb1e74ffb60c53fcd29793ce2cc79 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 14 May 2019 16:22:11 +0300 Subject: [PATCH 22/68] get data from cache --- .../controllers/api/rpc/block_controller.ex | 3 ++- .../explorer/lib/explorer/chain/block_number_cache.ex | 8 ++++++++ apps/indexer/lib/indexer/block/realtime/fetcher.ex | 11 ++++++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex index 31f3ad3d3d..8beb8ea8ea 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex @@ -3,6 +3,7 @@ defmodule BlockScoutWeb.API.RPC.BlockController do alias BlockScoutWeb.Chain, as: ChainWeb alias Explorer.Chain + alias Explorer.Chain.BlockNumberCache def getblockreward(conn, params) do with {:block_param, {:ok, unsafe_block_number}} <- {:block_param, Map.fetch(params, "blockno")}, @@ -26,7 +27,7 @@ defmodule BlockScoutWeb.API.RPC.BlockController do def eth_block_number(conn, params) do id = Map.get(params, "id", 1) - max_block_number = Chain.fetch_max_block_number() + max_block_number = BlockNumberCache.max_number() render(conn, :eth_block_number, number: max_block_number, id: id) end diff --git a/apps/explorer/lib/explorer/chain/block_number_cache.ex b/apps/explorer/lib/explorer/chain/block_number_cache.ex index 28d8d83e9c..c112747278 100644 --- a/apps/explorer/lib/explorer/chain/block_number_cache.ex +++ b/apps/explorer/lib/explorer/chain/block_number_cache.ex @@ -59,6 +59,14 @@ defmodule Explorer.Chain.BlockNumberCache do end end + def update_max_number(max) do + {min, _max, time} = cached_values() + + tuple = {min, max, time} + + :ets.insert(@tab, {@key, tuple}) + end + defp update_cache do current_time = current_time() {min, max} = min_and_max_from_db() diff --git a/apps/indexer/lib/indexer/block/realtime/fetcher.ex b/apps/indexer/lib/indexer/block/realtime/fetcher.ex index 8778677f33..a40e857c5f 100644 --- a/apps/indexer/lib/indexer/block/realtime/fetcher.ex +++ b/apps/indexer/lib/indexer/block/realtime/fetcher.ex @@ -27,6 +27,7 @@ defmodule Indexer.Block.Realtime.Fetcher do alias Ecto.Changeset alias EthereumJSONRPC.{FetchedBalances, Subscription} alias Explorer.Chain + alias Explorer.Chain.BlockNumberCache alias Explorer.Counters.AverageBlockTime alias Indexer.{Block, Tracer} alias Indexer.Block.Realtime.TaskSupervisor @@ -257,7 +258,9 @@ defmodule Indexer.Block.Realtime.Fetcher do {:ok, %{inserted: _, errors: []}} -> Logger.debug("Fetched and imported.") - {:ok, %{inserted: _, errors: [_ | _] = errors}} -> + {:ok, %{inserted: inserted, errors: [_ | _] = errors}} -> + update_block_cache(inserted["blocks"]) + Logger.error(fn -> [ "failed to fetch block: ", @@ -318,6 +321,12 @@ defmodule Indexer.Block.Realtime.Fetcher do end end + defp update_block_cache(blocks) do + max_block = Enum.max_by(blocks, fn block -> block.number end) + + BlockNumberCache.update_max_number(max_block.number) + end + defp retry_fetch_and_import_block(%{retry: retry}) when retry < 1, do: :ignore defp retry_fetch_and_import_block(%{changesets: changesets} = params) do From bd3e9def5d3adb747cfe48369563f494fc106245 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 14 May 2019 16:23:45 +0300 Subject: [PATCH 23/68] check if number is bigger --- apps/explorer/lib/explorer/chain/block_number_cache.ex | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/block_number_cache.ex b/apps/explorer/lib/explorer/chain/block_number_cache.ex index c112747278..2349ec9b36 100644 --- a/apps/explorer/lib/explorer/chain/block_number_cache.ex +++ b/apps/explorer/lib/explorer/chain/block_number_cache.ex @@ -60,11 +60,13 @@ defmodule Explorer.Chain.BlockNumberCache do end def update_max_number(max) do - {min, _max, time} = cached_values() + {min, old_max, time} = cached_values() - tuple = {min, max, time} + if max > old_max do + tuple = {min, max, time} - :ets.insert(@tab, {@key, tuple}) + :ets.insert(@tab, {@key, tuple}) + end end defp update_cache do From e6c86801fa46e7681d3fa70319340d138c4c7101 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 14 May 2019 16:57:00 +0300 Subject: [PATCH 24/68] fix CR issue --- apps/explorer/lib/explorer/chain.ex | 16 ---------------- .../lib/explorer/chain/block_number_cache.ex | 17 ++++++++++++----- .../lib/indexer/block/realtime/fetcher.ex | 2 +- 3 files changed, 13 insertions(+), 22 deletions(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 1f96200ae3..713d449d72 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -1059,22 +1059,6 @@ defmodule Explorer.Chain do end end - @spec fetch_max_block_number() :: non_neg_integer - def fetch_max_block_number do - query = - from(block in Block, - select: max(block.number), - where: block.consensus == true - ) - - result = Repo.one!(query) - - case result do - nil -> 0 - _ -> result - end - end - @spec fetch_count_consensus_block() :: non_neg_integer def fetch_count_consensus_block do query = diff --git a/apps/explorer/lib/explorer/chain/block_number_cache.ex b/apps/explorer/lib/explorer/chain/block_number_cache.ex index 2349ec9b36..72f169d4c1 100644 --- a/apps/explorer/lib/explorer/chain/block_number_cache.ex +++ b/apps/explorer/lib/explorer/chain/block_number_cache.ex @@ -59,13 +59,20 @@ defmodule Explorer.Chain.BlockNumberCache do end end - def update_max_number(max) do - {min, old_max, time} = cached_values() + def update(number) do + {old_min, old_max, time} = cached_values() - if max > old_max do - tuple = {min, max, time} + cond do + number > old_max -> + tuple = {old_min, number, time} + :ets.insert(@tab, {@key, tuple}) - :ets.insert(@tab, {@key, tuple}) + number < old_min -> + tuple = {number, old_max, time} + :ets.insert(@tab, {@key, tuple}) + + true -> + false end end diff --git a/apps/indexer/lib/indexer/block/realtime/fetcher.ex b/apps/indexer/lib/indexer/block/realtime/fetcher.ex index a40e857c5f..abff52c0c6 100644 --- a/apps/indexer/lib/indexer/block/realtime/fetcher.ex +++ b/apps/indexer/lib/indexer/block/realtime/fetcher.ex @@ -324,7 +324,7 @@ defmodule Indexer.Block.Realtime.Fetcher do defp update_block_cache(blocks) do max_block = Enum.max_by(blocks, fn block -> block.number end) - BlockNumberCache.update_max_number(max_block.number) + BlockNumberCache.update(max_block.number) end defp retry_fetch_and_import_block(%{retry: retry}) when retry < 1, do: :ignore From 833cdb384f86962ad50e25f9e0f45d17233d2b4a Mon Sep 17 00:00:00 2001 From: zachdaniel Date: Tue, 14 May 2019 09:59:14 -0400 Subject: [PATCH 25/68] feat: exclude empty contracts by default --- CHANGELOG.md | 1 + .../api/rpc/contract_controller.ex | 5 ++ .../lib/block_scout_web/etherscan.ex | 2 +- .../api/rpc/contract_controller_test.exs | 57 +++++++++++++++++++ apps/explorer/lib/explorer/chain.ex | 15 +++++ 5 files changed, 79 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0eed85aec8..02a2349bf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - [#1920](https://github.com/poanetwork/blockscout/pull/1920) - fix: remove source code fields from list endpoint - [#1876](https://github.com/poanetwork/blockscout/pull/1876) - async calculate a count of blocks - [#1941](https://github.com/poanetwork/blockscout/pull/1941) - feat: add on demand fetching and stale attr to rpc +- [#1952](https://github.com/poanetwork/blockscout/pull/1952) - feat: exclude empty contracts by default ### Fixes diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex index 886bbcd575..23dacd45da 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex @@ -103,6 +103,9 @@ defmodule BlockScoutWeb.API.RPC.ContractController do :not_decompiled -> Chain.list_not_decompiled_contracts(page_size, offset) + :empty -> + Chain.list_empty_contracts(page_size, offset) + _ -> Chain.list_contracts(page_size, offset) end @@ -140,10 +143,12 @@ defmodule BlockScoutWeb.API.RPC.ContractController do defp contracts_filter(2), do: {:ok, :decompiled} defp contracts_filter(3), do: {:ok, :unverified} defp contracts_filter(4), do: {:ok, :not_decompiled} + defp contracts_filter(5), do: {:ok, :empty} defp contracts_filter("verified"), do: {:ok, :verified} defp contracts_filter("decompiled"), do: {:ok, :decompiled} defp contracts_filter("unverified"), do: {:ok, :unverified} defp contracts_filter("not_decompiled"), do: {:ok, :not_decompiled} + defp contracts_filter("empty"), do: {:ok, :empty} defp contracts_filter(filter) when is_bitstring(filter) do case Integer.parse(filter) do 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 3621a12d10..a657ff8fd2 100644 --- a/apps/block_scout_web/lib/block_scout_web/etherscan.ex +++ b/apps/block_scout_web/lib/block_scout_web/etherscan.ex @@ -1795,7 +1795,7 @@ defmodule BlockScoutWeb.Etherscan do key: "filter", type: "string", description: - "verified|decompiled|unverified|not_decompiled, or 1|2|3|4 respectively. This requests only contracts with that status." + "verified|decompiled|unverified|not_decompiled|empty, or 1|2|3|4|5 respectively. This requests only contracts with that status." }, %{ key: "not_decompiled_with_version", 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 43169a65d2..399e49bfff 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 @@ -100,6 +100,34 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do ] end + test "filtering for only unverified contracts does not show self destructed contracts", %{ + params: params, + conn: conn + } do + address = insert(:contract_address) + insert(:smart_contract) + insert(:contract_address, contract_code: "0x") + + response = + conn + |> get("/api", Map.put(params, "filter", "unverified")) + |> json_response(200) + + assert response["message"] == "OK" + assert response["status"] == "1" + + assert response["result"] == [ + %{ + "ABI" => "Contract source code not verified", + "Address" => to_string(address.hash), + "CompilerVersion" => "", + "ContractName" => "", + "DecompilerVersion" => "", + "OptimizationUsed" => "" + } + ] + end + test "filtering for only verified contracts shows only verified contracts", %{params: params, conn: conn} do insert(:contract_address) contract = insert(:smart_contract) @@ -222,6 +250,35 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do } ] end + + test "filtering for only not_decompiled (and by extension not verified contracts) does not show empty contracts", %{ + params: params, + conn: conn + } do + insert(:decompiled_smart_contract) + insert(:smart_contract) + insert(:contract_address, contract_code: "0x") + contract_address = insert(:contract_address) + + response = + conn + |> get("/api", Map.put(params, "filter", "not_decompiled")) + |> json_response(200) + + assert response["message"] == "OK" + assert response["status"] == "1" + + assert response["result"] == [ + %{ + "ABI" => "Contract source code not verified", + "Address" => to_string(contract_address.hash), + "CompilerVersion" => "", + "ContractName" => "", + "DecompilerVersion" => "", + "OptimizationUsed" => "" + } + ] + end end describe "getabi" do diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 713d449d72..adb9759638 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -2802,6 +2802,7 @@ defmodule Explorer.Chain do on: smart_contract.address_hash == address.hash, where: not is_nil(address.contract_code), where: is_nil(smart_contract.address_hash), + where: address.contract_code != <<>>, preload: [{:smart_contract, smart_contract}, :decompiled_smart_contracts], order_by: [asc: address.inserted_at], limit: ^limit, @@ -2811,6 +2812,19 @@ defmodule Explorer.Chain do Repo.all(query) end + def list_empty_contracts(limit, offset) do + query = + from(address in Address, + where: address.contract_code == <<>>, + preload: [:smart_contract, :decompiled_smart_contracts], + order_by: [asc: address.inserted_at], + limit: ^limit, + offset: ^offset + ) + + Repo.all(query) + end + def list_not_decompiled_contracts(limit, offset) do query = from( @@ -2820,6 +2834,7 @@ defmodule Explorer.Chain do "NOT EXISTS (SELECT 1 FROM decompiled_smart_contracts WHERE decompiled_smart_contracts.address_hash = ?)", address.hash ), + where: address.contract_code != <<>>, left_join: smart_contract in SmartContract, on: smart_contract.address_hash == address.hash, left_join: decompiled_smart_contract in DecompiledSmartContract, From b9160f2175cd6633a2f08d8ed30fc33b395529b8 Mon Sep 17 00:00:00 2001 From: saneery Date: Tue, 14 May 2019 16:27:59 +0300 Subject: [PATCH 26/68] calculate staked ratio after insert pools --- .../chain/import/runner/staking_pools.ex | 16 +- .../import/runner/staking_pools_test.exs | 138 ++++++++++-------- 2 files changed, 90 insertions(+), 64 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex b/apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex index aaf5d7242e..17a13dd5bc 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex @@ -62,7 +62,7 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do {:ok, _} = Import.insert_changes_list( repo, - changes_list, + stakes_ratio(changes_list), conflict_target: {:unsafe_fragment, "(address_hash) where \"primary\" = true"}, on_conflict: on_conflict, for: Address.Name, @@ -85,4 +85,18 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do ] ) end + + # Calculates staked ratio for each pool + defp stakes_ratio(pools) do + active_pools = Enum.filter(pools, & &1.metadata[:is_active]) + + stakes_total = Enum.reduce(pools, 0, fn pool, acc -> + acc + pool.metadata[:staked_amount] + end) + + Enum.map(active_pools, fn pool -> + staked_ratio = pool.metadata[:staked_amount] / stakes_total + put_in(pool, [:metadata, :staked_ratio], staked_ratio) + end) + end end diff --git a/apps/explorer/test/explorer/chain/import/runner/staking_pools_test.exs b/apps/explorer/test/explorer/chain/import/runner/staking_pools_test.exs index d5bc6ecfca..5f5ec5a3be 100644 --- a/apps/explorer/test/explorer/chain/import/runner/staking_pools_test.exs +++ b/apps/explorer/test/explorer/chain/import/runner/staking_pools_test.exs @@ -6,80 +6,92 @@ defmodule Explorer.Chain.Import.Runner.StakingPoolsTest do describe "run/1" do test "insert new pools list" do - pools = [ - %{ - address_hash: %Explorer.Chain.Hash{ - byte_count: 20, - bytes: <<11, 47, 94, 47, 60, 189, 134, 78, 170, 44, 100, 46, 55, 105, 193, 88, 35, 97, 202, 246>> - }, - metadata: %{ - banned_unitil: 0, - delegators_count: 0, - is_active: true, - is_banned: false, - is_validator: true, - mining_address: %Explorer.Chain.Hash{ + pools = + [pool1, pool2, pool3] = [ + %{ + address_hash: %Explorer.Chain.Hash{ byte_count: 20, - bytes: <<187, 202, 168, 212, 130, 137, 187, 31, 252, 249, 128, 141, 154, 164, 177, 210, 21, 5, 76, 120>> + bytes: <<11, 47, 94, 47, 60, 189, 134, 78, 170, 44, 100, 46, 55, 105, 193, 88, 35, 97, 202, 246>> }, - retries_count: 1, - staked_amount: 0, - was_banned_count: 0, - was_validator_count: 1 - }, - name: "anonymous", - primary: true - }, - %{ - address_hash: %Explorer.Chain.Hash{ - byte_count: 20, - bytes: <<170, 148, 182, 135, 211, 249, 85, 42, 69, 59, 129, 178, 131, 76, 165, 55, 120, 152, 13, 192>> + metadata: %{ + banned_unitil: 0, + delegators_count: 0, + is_active: true, + is_banned: false, + is_validator: true, + mining_address: %Explorer.Chain.Hash{ + byte_count: 20, + bytes: <<187, 202, 168, 212, 130, 137, 187, 31, 252, 249, 128, 141, 154, 164, 177, 210, 21, 5, 76, 120>> + }, + retries_count: 1, + staked_amount: 25, + was_banned_count: 0, + was_validator_count: 1 + }, + name: "anonymous", + primary: true }, - metadata: %{ - banned_unitil: 0, - delegators_count: 0, - is_active: true, - is_banned: false, - is_validator: true, - mining_address: %Explorer.Chain.Hash{ + %{ + address_hash: %Explorer.Chain.Hash{ byte_count: 20, - bytes: <<117, 223, 66, 56, 58, 254, 107, 245, 25, 74, 168, 250, 14, 155, 61, 95, 158, 134, 148, 65>> + bytes: <<170, 148, 182, 135, 211, 249, 85, 42, 69, 59, 129, 178, 131, 76, 165, 55, 120, 152, 13, 192>> }, - retries_count: 1, - staked_amount: 0, - was_banned_count: 0, - was_validator_count: 1 - }, - name: "anonymous", - primary: true - }, - %{ - address_hash: %Explorer.Chain.Hash{ - byte_count: 20, - bytes: <<49, 44, 35, 14, 125, 109, 176, 82, 36, 246, 2, 8, 166, 86, 227, 84, 28, 92, 66, 186>> + metadata: %{ + banned_unitil: 0, + delegators_count: 0, + is_active: true, + is_banned: false, + is_validator: true, + mining_address: %Explorer.Chain.Hash{ + byte_count: 20, + bytes: <<117, 223, 66, 56, 58, 254, 107, 245, 25, 74, 168, 250, 14, 155, 61, 95, 158, 134, 148, 65>> + }, + retries_count: 1, + staked_amount: 25, + was_banned_count: 0, + was_validator_count: 1 + }, + name: "anonymous", + primary: true }, - metadata: %{ - banned_unitil: 0, - delegators_count: 0, - is_active: true, - is_banned: false, - is_validator: true, - mining_address: %Explorer.Chain.Hash{ + %{ + address_hash: %Explorer.Chain.Hash{ byte_count: 20, - bytes: <<82, 45, 243, 150, 174, 112, 160, 88, 189, 105, 119, 132, 8, 99, 15, 219, 2, 51, 137, 178>> + bytes: <<49, 44, 35, 14, 125, 109, 176, 82, 36, 246, 2, 8, 166, 86, 227, 84, 28, 92, 66, 186>> }, - retries_count: 1, - staked_amount: 0, - was_banned_count: 0, - was_validator_count: 1 - }, - name: "anonymous", - primary: true - } - ] + metadata: %{ + banned_unitil: 0, + delegators_count: 0, + is_active: true, + is_banned: false, + is_validator: true, + mining_address: %Explorer.Chain.Hash{ + byte_count: 20, + bytes: <<82, 45, 243, 150, 174, 112, 160, 88, 189, 105, 119, 132, 8, 99, 15, 219, 2, 51, 137, 178>> + }, + retries_count: 1, + staked_amount: 0, + was_banned_count: 0, + was_validator_count: 1 + }, + name: "anonymous", + primary: true + } + ] assert {:ok, %{insert_staking_pools: list}} = run_changes(pools) assert Enum.count(list) == Enum.count(pools) + + saved_list = + Explorer.Chain.Address.Name + |> Repo.all() + |> Enum.reduce(%{}, fn pool, acc -> + Map.put(acc, pool.address_hash, pool) + end) + + assert saved_list[pool1.address_hash].metadata["staked_ratio"] == 0.5 + assert saved_list[pool2.address_hash].metadata["staked_ratio"] == 0.5 + assert saved_list[pool3.address_hash].metadata["staked_ratio"] == 0.0 end end From 0fba00a3e5d3efec8677860e205e805fd34008e5 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Tue, 14 May 2019 19:43:22 +0300 Subject: [PATCH 27/68] add typespec --- apps/explorer/lib/explorer/chain/block_number_cache.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/explorer/lib/explorer/chain/block_number_cache.ex b/apps/explorer/lib/explorer/chain/block_number_cache.ex index 72f169d4c1..d15d6781e6 100644 --- a/apps/explorer/lib/explorer/chain/block_number_cache.ex +++ b/apps/explorer/lib/explorer/chain/block_number_cache.ex @@ -59,6 +59,7 @@ defmodule Explorer.Chain.BlockNumberCache do end end + @spec update(non_neg_integer()) :: boolean() def update(number) do {old_min, old_max, time} = cached_values() From cc45d14f0ede94af998a345a0d6b0c634f4b3290 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Wed, 15 May 2019 11:44:30 +0300 Subject: [PATCH 28/68] add events logs query --- apps/explorer/lib/explorer/chain.ex | 34 +++++++++++++++ apps/explorer/test/explorer/chain_test.exs | 50 ++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 713d449d72..22d0a0cd6a 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -279,6 +279,40 @@ defmodule Explorer.Chain do |> Enum.take(paging_options.page_size) end + @spec address_to_logs(Address.t(), [paging_options]) :: [ + Log.t() + ] + def address_to_logs( + %Address{hash: %Hash{byte_count: unquote(Hash.Address.byte_count())} = address_hash}, + options \\ [] + ) + when is_list(options) do + paging_options = Keyword.get(options, :paging_options) + + {block_number, transaction_index, log_index} = paging_options.key || {BlockNumberCache.max_number(), 0, 0} + + query = + from(transaction in Transaction, + inner_join: log in assoc(transaction, :logs), + order_by: [desc: transaction.block_number, desc: transaction.index], + preload: [:logs], + where: + (transaction.from_address_hash == ^address_hash or transaction.to_address_hash == ^address_hash or + transaction.created_contract_address_hash == ^address_hash) and log.address_hash == ^address_hash and + (transaction.block_number < ^block_number or + (transaction.block_number == ^block_number and transaction.index > ^transaction_index) or + (transaction.block_number == ^block_number and transaction.index == ^transaction_index and + log.index > ^log_index)), + limit: ^paging_options.page_size, + distinct: transaction.hash + ) + + query + |> Repo.all() + |> Enum.flat_map(fn transaction -> transaction.logs end) + |> Enum.take(paging_options.page_size) + end + @doc """ Finds all `t:Explorer.Chain.Transaction.t/0`s given the address_hash and the token contract address hash. diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index a9b479c3bd..201090112b 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -50,6 +50,56 @@ defmodule Explorer.ChainTest do end end + describe "address_to_logs/2" do + test "fetches logs" do + address = insert(:address) + + transaction1 = + :transaction + |> insert(to_address: address) + |> with_block() + + insert(:log, transaction: transaction1, index: 1, address: address) + + transaction2 = + :transaction + |> insert(from_address: address) + |> with_block() + + insert(:log, transaction: transaction2, index: 2, address: address) + + assert Enum.count(Chain.address_to_logs(address)) == 2 + end + + test "paginates logs" do + address = insert(:address) + + transaction = + :transaction + |> insert(to_address: address) + |> with_block() + + log1 = insert(:log, transaction: transaction, index: 1, address: address) + + second_page_indexes = + 2..51 + |> Enum.map(fn index -> insert(:log, transaction: transaction, index: index, address: address) end) + |> Enum.map(& &1.index) + + paging_options1 = %PagingOptions{page_size: 1} + + [log] = Chain.address_to_logs(address, paging_options: paging_options1) + assert log.index == log1.index + + paging_options2 = %PagingOptions{page_size: 60, key: {transaction.block_number, transaction.index, log1.index}} + + returned_log_indexes = + Chain.address_to_logs(address, paging_options: paging_options2) |> Enum.map(fn log -> log.index end) + + assert second_page_indexes == returned_log_indexes + end + end + describe "address_to_transactions_with_rewards/2" do test "without transactions" do address = insert(:address) From 18fcd41d420e41c2a05757e53b22bb1c2a76cd78 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Wed, 15 May 2019 12:10:03 +0300 Subject: [PATCH 29/68] add CHANGELOG entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0eed85aec8..de6e381818 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - [#1920](https://github.com/poanetwork/blockscout/pull/1920) - fix: remove source code fields from list endpoint - [#1876](https://github.com/poanetwork/blockscout/pull/1876) - async calculate a count of blocks - [#1941](https://github.com/poanetwork/blockscout/pull/1941) - feat: add on demand fetching and stale attr to rpc +- [#1933](https://github.com/poanetwork/blockscout/pull/1933) - add eth_BlockNumber json rpc method ### Fixes From 822986c13554a75a022291cade4a00cb22dfa831 Mon Sep 17 00:00:00 2001 From: saneery Date: Wed, 15 May 2019 12:19:57 +0300 Subject: [PATCH 30/68] format --- .../lib/explorer/chain/import/runner/staking_pools.ex | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex b/apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex index 17a13dd5bc..95c3c62e55 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex @@ -90,9 +90,10 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do defp stakes_ratio(pools) do active_pools = Enum.filter(pools, & &1.metadata[:is_active]) - stakes_total = Enum.reduce(pools, 0, fn pool, acc -> - acc + pool.metadata[:staked_amount] - end) + stakes_total = + Enum.reduce(pools, 0, fn pool, acc -> + acc + pool.metadata[:staked_amount] + end) Enum.map(active_pools, fn pool -> staked_ratio = pool.metadata[:staked_amount] / stakes_total From 3a44d6caf4012f8c4ee0630f49e4e0c591dbfb09 Mon Sep 17 00:00:00 2001 From: saneery Date: Wed, 15 May 2019 12:21:23 +0300 Subject: [PATCH 31/68] add entry to the changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 864fb2099e..31183dc8ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - [#1920](https://github.com/poanetwork/blockscout/pull/1920) - fix: remove source code fields from list endpoint - [#1876](https://github.com/poanetwork/blockscout/pull/1876) - async calculate a count of blocks - [#1941](https://github.com/poanetwork/blockscout/pull/1941) - feat: add on demand fetching and stale attr to rpc +- [#1957](https://github.com/poanetwork/blockscout/pull/1957) - Calculate stakes ratio before insert pools ### Fixes From e993c95e4bf6258db602cafb0728ebba68786197 Mon Sep 17 00:00:00 2001 From: saneery Date: Wed, 15 May 2019 12:55:02 +0300 Subject: [PATCH 32/68] handle case when total stakes is zero --- .../explorer/lib/explorer/chain/import/runner/staking_pools.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex b/apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex index 95c3c62e55..457175a9e4 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex @@ -96,7 +96,8 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do end) Enum.map(active_pools, fn pool -> - staked_ratio = pool.metadata[:staked_amount] / stakes_total + staked_ratio = if stakes_total > 0, do: pool.metadata[:staked_amount] / stakes_total, else: 0 + put_in(pool, [:metadata, :staked_ratio], staked_ratio) end) end From d05e4d53fa81530a3d975e74c624c8507a816846 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Wed, 15 May 2019 13:43:43 +0300 Subject: [PATCH 33/68] fix CR issues --- .../views/api/rpc/block_view.ex | 22 +++++++++++++++++-- .../views/api/rpc/eth_rpc_view.ex | 8 +++---- apps/indexer/lib/indexer/block/fetcher.ex | 14 ++++++++++-- .../lib/indexer/block/realtime/fetcher.ex | 11 +--------- 4 files changed, 37 insertions(+), 18 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/block_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/block_view.ex index 9b3460af49..5c98794b2a 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/block_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/block_view.ex @@ -23,7 +23,7 @@ defmodule BlockScoutWeb.API.RPC.BlockView do end def render("eth_block_number.json", %{number: number, id: id}) do - result = integer_to_hex(number) + result = encode_quantity(number) EthRPCView.render("show.json", %{result: result, id: id}) end @@ -32,5 +32,23 @@ defmodule BlockScoutWeb.API.RPC.BlockView do RPCView.render("error.json", error: error) end - defp integer_to_hex(integer), do: Integer.to_string(integer, 16) + defp encode_quantity(binary) when is_binary(binary) do + hex_binary = Base.encode16(binary, case: :lower) + + result = String.replace_leading(hex_binary, "0", "") + + final_result = if result == "", do: "0", else: result + + "0x#{final_result}" + end + + defp encode_quantity(value) when is_integer(value) do + value + |> :binary.encode_unsigned() + |> encode_quantity() + end + + defp encode_quantity(value) when is_nil(value) do + nil + end end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/eth_rpc_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/eth_rpc_view.ex index f82e476221..316fe58943 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/eth_rpc_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/eth_rpc_view.ex @@ -3,17 +3,17 @@ defmodule BlockScoutWeb.API.RPC.EthRPCView do def render("show.json", %{result: result, id: id}) do %{ - "id" => id, "jsonrpc" => "2.0", - "result" => result + "result" => result, + "id" => id } end def render("error.json", %{error: message, id: id}) do %{ - "id" => id, "jsonrpc" => "2.0", - "error" => message + "error" => message, + "id" => id } end end diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex index deb0e4554f..06a702aa4b 100644 --- a/apps/indexer/lib/indexer/block/fetcher.ex +++ b/apps/indexer/lib/indexer/block/fetcher.ex @@ -11,7 +11,7 @@ defmodule Indexer.Block.Fetcher do alias EthereumJSONRPC.{Blocks, FetchedBeneficiaries} alias Explorer.Chain - alias Explorer.Chain.{Address, Block, Hash, Import, Transaction} + alias Explorer.Chain.{Address, Block, BlockNumberCache, Hash, Import, Transaction} alias Indexer.Block.Fetcher.Receipts alias Indexer.Fetcher.{ @@ -171,13 +171,23 @@ defmodule Indexer.Block.Fetcher do transactions: %{params: transactions_with_receipts} } ) do - {:ok, %{inserted: inserted, errors: blocks_errors}} + result = {:ok, %{inserted: inserted, errors: blocks_errors}} + update_block_cache(inserted[:blocks]) + result else {step, {:error, reason}} -> {:error, {step, reason}} {:import, {:error, step, failed_value, changes_so_far}} -> {:error, {step, failed_value, changes_so_far}} end end + defp update_block_cache(blocks) do + max_block = Enum.max_by(blocks, fn block -> block.number end) + min_block = Enum.min_by(blocks, fn block -> block.number end) + + BlockNumberCache.update(max_block.number) + BlockNumberCache.update(min_block.number) + end + def import( %__MODULE__{broadcast: broadcast, callback_module: callback_module} = state, options diff --git a/apps/indexer/lib/indexer/block/realtime/fetcher.ex b/apps/indexer/lib/indexer/block/realtime/fetcher.ex index abff52c0c6..8778677f33 100644 --- a/apps/indexer/lib/indexer/block/realtime/fetcher.ex +++ b/apps/indexer/lib/indexer/block/realtime/fetcher.ex @@ -27,7 +27,6 @@ defmodule Indexer.Block.Realtime.Fetcher do alias Ecto.Changeset alias EthereumJSONRPC.{FetchedBalances, Subscription} alias Explorer.Chain - alias Explorer.Chain.BlockNumberCache alias Explorer.Counters.AverageBlockTime alias Indexer.{Block, Tracer} alias Indexer.Block.Realtime.TaskSupervisor @@ -258,9 +257,7 @@ defmodule Indexer.Block.Realtime.Fetcher do {:ok, %{inserted: _, errors: []}} -> Logger.debug("Fetched and imported.") - {:ok, %{inserted: inserted, errors: [_ | _] = errors}} -> - update_block_cache(inserted["blocks"]) - + {:ok, %{inserted: _, errors: [_ | _] = errors}} -> Logger.error(fn -> [ "failed to fetch block: ", @@ -321,12 +318,6 @@ defmodule Indexer.Block.Realtime.Fetcher do end end - defp update_block_cache(blocks) do - max_block = Enum.max_by(blocks, fn block -> block.number end) - - BlockNumberCache.update(max_block.number) - end - defp retry_fetch_and_import_block(%{retry: retry}) when retry < 1, do: :ignore defp retry_fetch_and_import_block(%{changesets: changesets} = params) do From 17a9ee44e7aee2d7075a98afdfd37b48509b3c6e Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Wed, 15 May 2019 14:10:25 +0300 Subject: [PATCH 34/68] add docs --- .../lib/block_scout_web/etherscan.ex | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) 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 3621a12d10..89c2927a9e 100644 --- a/apps/block_scout_web/lib/block_scout_web/etherscan.ex +++ b/apps/block_scout_web/lib/block_scout_web/etherscan.ex @@ -279,6 +279,12 @@ defmodule BlockScoutWeb.Etherscan do "result" => nil } + @block_eth_block_number_example_value %{ + "jsonrpc" => "2.0", + "result" => "767969", + "id" => 1 + } + @contract_listcontracts_example_value %{ "status" => "1", "message" => "OK", @@ -476,11 +482,26 @@ defmodule BlockScoutWeb.Etherscan do enum_interpretation: %{"0" => "error", "1" => "ok"} } + @jsonrpc_version_type %{ + type: "string", + example: ~s("2.0") + } + @message_type %{ type: "string", example: ~s("OK") } + @hex_number_type %{ + type: "string", + example: ~s("767969") + } + + @id_type %{ + type: "string", + example: ~s("1") + } + @wei_type %{ type: "wei", definition: &__MODULE__.wei_type_definition/1, @@ -1737,6 +1758,35 @@ defmodule BlockScoutWeb.Etherscan do ] } + @block_eth_block_number_action %{ + name: "eth_block_number", + description: "Mimics Ethereum JSON RPC's eth_blockNumber. Returns the lastest block number", + required_params: [], + optional_params: [ + %{ + key: "id", + placeholder: "request id", + type: "integer", + description: "A nonnegative integer that represents the json rpc request id." + } + ], + responses: [ + %{ + code: "200", + description: "successful request", + example_value: Jason.encode!(@block_eth_block_number_example_value), + model: %{ + name: "Result", + fields: %{ + jsonrpc: @jsonrpc_version_type, + id: @id_type, + result: @hex_number_type + } + } + } + ] + } + @block_getblockreward_action %{ name: "getblockreward", description: "Get block reward by block number.", @@ -2171,7 +2221,7 @@ defmodule BlockScoutWeb.Etherscan do @block_module %{ name: "block", - actions: [@block_getblockreward_action] + actions: [@block_getblockreward_action, @block_eth_block_number_action] } @contract_module %{ From b04d99a0be96c0d38946b3a4d259b2a08df9e6ab Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Wed, 15 May 2019 14:20:11 +0300 Subject: [PATCH 35/68] Default value for release link --- .../lib/block_scout_web/views/layout_view.ex | 13 ++++++++----- .../test/block_scout_web/views/layout_view_test.exs | 6 ++++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex b/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex index 7a454a0e72..8698dc7a7b 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex @@ -144,11 +144,14 @@ defmodule BlockScoutWeb.LayoutView do def release_link(version) do release_link = Application.get_env(:block_scout_web, :release_link) - if release_link == "" || release_link == nil do - version - else - html_escape({:safe, "
#{version}"}) - end + release_link = + if release_link == "" || release_link == nil do + "https://github.com/poanetwork/blockscout/releases/tag/" <> version + else + release_link + end + + html_escape({:safe, "#{version}"}) end def ignore_version?("unknown"), do: true diff --git a/apps/block_scout_web/test/block_scout_web/views/layout_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/layout_view_test.exs index 5e3302ceec..5a934c20be 100644 --- a/apps/block_scout_web/test/block_scout_web/views/layout_view_test.exs +++ b/apps/block_scout_web/test/block_scout_web/views/layout_view_test.exs @@ -65,13 +65,15 @@ defmodule BlockScoutWeb.LayoutViewTest do test "use the version when there is no release_link env configured for it" do Application.put_env(:block_scout_web, :release_link, nil) - assert LayoutView.release_link("1.3.4") == "1.3.4" + assert LayoutView.release_link("1.3.4") == {:safe, + ~s(1.3.4)} end test "use the version when empty release_link env configured for it" do Application.put_env(:block_scout_web, :release_link, "") - assert LayoutView.release_link("1.3.4") == "1.3.4" + assert LayoutView.release_link("1.3.4") == {:safe, + ~s(1.3.4)} end test "use the enviroment release link when it's configured" do From b2a3e7d59d8218209bde75048da12b34c67bb38d Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Wed, 15 May 2019 14:33:24 +0300 Subject: [PATCH 36/68] Fix various tests --- .dialyzer-ignore | 2 +- .../lib/block_scout_web/views/layout_view.ex | 6 +++--- .../test/block_scout_web/views/layout_view_test.exs | 6 ++++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.dialyzer-ignore b/.dialyzer-ignore index 11bb3be729..e6b6ee70a7 100644 --- a/.dialyzer-ignore +++ b/.dialyzer-ignore @@ -4,4 +4,4 @@ apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex:400: Function timestamp_to_datetime/1 has no local return apps/explorer/lib/explorer/repo/prometheus_logger.ex:8: Function microseconds_time/1 has no local return apps/explorer/lib/explorer/repo/prometheus_logger.ex:8: The call 'Elixir.System':convert_time_unit(__@1::any(),'native','microseconds') breaks the contract (integer(),time_unit() | 'native',time_unit() | 'native') -> integer() -apps/block_scout_web/lib/block_scout_web/views/layout_view.ex:162: The call 'Elixir.Poison.Parser':'parse!'(any(),#{'keys':='atoms!'}) will never return since the success typing is (binary() | maybe_improper_list(binary() | maybe_improper_list(any(),binary() | []) | byte(),binary() | []),[{atom(),_}]) -> 'false' | 'nil' | 'true' | binary() | ['false' | 'nil' | 'true' | binary() | [any()] | number() | map()] | number() | map() and the contract is (iodata(),'Elixir.Keyword':t()) -> t() \ No newline at end of file +apps/block_scout_web/lib/block_scout_web/views/layout_view.ex:165: The call 'Elixir.Poison.Parser':'parse!'(any(),#{'keys':='atoms!'}) will never return since the success typing is (binary() | maybe_improper_list(binary() | maybe_improper_list(any(),binary() | []) | byte(),binary() | []),[{atom(),_}]) -> 'false' | 'nil' | 'true' | binary() | ['false' | 'nil' | 'true' | binary() | [any()] | number() | map()] | number() | map() and the contract is (iodata(),'Elixir.Keyword':t()) -> t() \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex b/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex index 8698dc7a7b..2a216d13fd 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex @@ -142,13 +142,13 @@ defmodule BlockScoutWeb.LayoutView do end def release_link(version) do - release_link = Application.get_env(:block_scout_web, :release_link) + release_link_env_var = Application.get_env(:block_scout_web, :release_link) release_link = - if release_link == "" || release_link == nil do + if release_link_env_var == "" || release_link_env_var == nil do "https://github.com/poanetwork/blockscout/releases/tag/" <> version else - release_link + release_link_env_var end html_escape({:safe, "#{version}"}) diff --git a/apps/block_scout_web/test/block_scout_web/views/layout_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/layout_view_test.exs index 5a934c20be..de693dac05 100644 --- a/apps/block_scout_web/test/block_scout_web/views/layout_view_test.exs +++ b/apps/block_scout_web/test/block_scout_web/views/layout_view_test.exs @@ -65,14 +65,16 @@ defmodule BlockScoutWeb.LayoutViewTest do test "use the version when there is no release_link env configured for it" do Application.put_env(:block_scout_web, :release_link, nil) - assert LayoutView.release_link("1.3.4") == {:safe, + assert LayoutView.release_link("1.3.4") == + {:safe, ~s(1.3.4)} end test "use the version when empty release_link env configured for it" do Application.put_env(:block_scout_web, :release_link, "") - assert LayoutView.release_link("1.3.4") == {:safe, + assert LayoutView.release_link("1.3.4") == + {:safe, ~s(1.3.4)} end From 9cb94ffa91f254cb416cffe71fd7347f3accb31f Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Wed, 15 May 2019 14:34:55 +0300 Subject: [PATCH 37/68] Update CHANGELOG entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0eed85aec8..676b95a4ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ - [#1837](https://github.com/poanetwork/blockscout/pull/1837) - Add -f flag to clear_build.sh script delete static folder - [#1900](https://github.com/poanetwork/blockscout/pull/1900) - SUPPORTED_CHAINS ENV var - [#1892](https://github.com/poanetwork/blockscout/pull/1892) - Remove temporary worker modules +- [#1958](https://github.com/poanetwork/blockscout/pull/1958) - Default value for release link env var ## 1.3.10-beta From 40f845ad4f6f47c5d85e15176e9ea80a100f9e80 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Wed, 15 May 2019 14:58:59 +0300 Subject: [PATCH 38/68] add order to response --- .../views/api/rpc/eth_rpc_view.ex | 38 +++++++++++++++---- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/eth_rpc_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/eth_rpc_view.ex index 316fe58943..e39cedf3a5 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/eth_rpc_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/eth_rpc_view.ex @@ -1,19 +1,41 @@ defmodule BlockScoutWeb.API.RPC.EthRPCView do use BlockScoutWeb, :view + defstruct [:result, :id, :error] + def render("show.json", %{result: result, id: id}) do - %{ - "jsonrpc" => "2.0", - "result" => result, - "id" => id + %__MODULE__{ + result: result, + id: id } end def render("error.json", %{error: message, id: id}) do - %{ - "jsonrpc" => "2.0", - "error" => message, - "id" => id + %__MODULE__{ + error: message, + id: id } end + + defimpl Poison.Encoder, for: BlockScoutWeb.API.RPC.EthRPCView do + def encode(%BlockScoutWeb.API.RPC.EthRPCView{result: result, id: id, error: error}, _options) when is_nil(error) do + """ + { + "jsonrpc": "2.0", + "result": "#{result}", + "id": #{id} + }\n + """ + end + + def encode(%BlockScoutWeb.API.RPC.EthRPCView{id: id, error: error}, _options) do + """ + { + "jsonrpc": "2.0", + "error": #{error}, + "id": #{id} + }\n + """ + end + end end From 8cfb85a9e6e197ee1700f78015d4e767ff7f52a1 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Wed, 15 May 2019 15:53:40 +0300 Subject: [PATCH 39/68] do not remove bold text --- .../views/address_decompiled_contract_view.ex | 5 +++- .../address_decompiled_contract_view_test.exs | 24 ++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_decompiled_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_decompiled_contract_view.ex index f80a0ded99..b92d356479 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_decompiled_contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_decompiled_contract_view.ex @@ -165,7 +165,10 @@ defmodule BlockScoutWeb.AddressDecompiledContractView do |> String.replace("\e[1m", "") |> String.replace("»", "»") |> String.replace("\e[0m", "") - |> String.split(~r/\|\<\/span\>/, include_captures: true, trim: true) + |> String.split(~r/\|\|\<\/span\>/, + include_captures: true, + trim: true + ) |> add_styles_to_every_line() result diff --git a/apps/block_scout_web/test/block_scout_web/views/address_decompiled_contract_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/address_decompiled_contract_view_test.exs index 8f1636ba06..334ea36baa 100644 --- a/apps/block_scout_web/test/block_scout_web/views/address_decompiled_contract_view_test.exs +++ b/apps/block_scout_web/test/block_scout_web/views/address_decompiled_contract_view_test.exs @@ -56,7 +56,7 @@ defmodule BlockScoutWeb.AddressDecompiledContractViewTest do result = AddressDecompiledContractView.highlight_decompiled_code(code) assert result == - " #\n # eveem.org 6 Feb 2019\n # Decompiled source of 0x00Bd9e214FAb74d6fC21bf1aF34261765f57e875\n #\n # Let's make the world open source\n # \n #\n # I failed with these:\n # - unknowne77c646d(?)\n # - transferFromWithData(address _from, address _to, uint256 _value, bytes _data)\n # All the rest is below.\n #\n\n\n # Storage definitions and getters\n\n def storage:\n allowance is uint256 => uint256 # mask(256, 0) at storage #2\n stor4 is uint256 => uint8 # mask(8, 0) at storage #4\n\n def allowance(address _owner, address _spender) payable: 64\n return allowance[_owner_spender(320 - 1))]\n\n\n #\n # Regular functions - see Tutorial for understanding quirks of the code\n #\n\n\n # folder failed in this function - may be terribly long, sorry\n def unknownc47d033b(?) payable: not cd[4]:\n revert\n else:\n mem[0]cd[4]\n mem[32] = 4\n mem[96] = bool(stor4[cd[4])])\n return bool(stor4[cd[4])])\n\n def _fallback() payable: # default function\n revert\n\n\n" + " #\n # eveem.org 6 Feb 2019\n # Decompiled source of 0x00Bd9e214FAb74d6fC21bf1aF34261765f57e875\n #\n # Let's make the world open source\n # \n #\n # I failed with these:\n # - unknowne77c646d(?)\n # - transferFromWithData(address _from, address _to, uint256 _value, bytes _data)\n # All the rest is below.\n #\n\n\n # Storage definitions and getters\n\n def storage:\n allowance is uint256 => uint256 # mask(256, 0) at storage #2\n stor4 is uint256 => uint8 # mask(8, 0) at storage #4\n\n def allowance(address _owner, address _spender) payable: \n require (calldata.size - 4) >= 64\n return allowance[sha3(((320 - 1) and (320 - 1) and _owner), 1), ((320 - 1) and _spender and (320 - 1))]\n\n\n #\n # Regular functions - see Tutorial for understanding quirks of the code\n #\n\n\n # folder failed in this function - may be terribly long, sorry\n def unknownc47d033b(?) payable: \n if (calldata.size - 4) < 32:\n revert\n else:\n if not (320 - 1) or not cd[4]:\n revert\n else:\n mem[0] = (320 - 1) and (320 - 1) and cd[4]\n mem[32] = 4\n mem[96] = bool(stor4[((320 - 1) and (320 - 1) and cd[4])])\n return bool(stor4[((320 - 1) and (320 - 1) and cd[4])])\n\n def _fallback() payable: # default function\n revert\n\n\n" end test "adds style span to every line" do @@ -72,6 +72,28 @@ defmodule BlockScoutWeb.AddressDecompiledContractViewTest do assert AddressDecompiledContractView.highlight_decompiled_code(code) == " #\n # eveem.org 6 Feb 2019\n # Decompiled source of 0x00Bd9e214FAb74d6fC21bf1aF34261765f57e875\n #\n # Let's make the world open source\n # \n\n\n" end + + test "does not remove bold text" do + code = """ + # + # Eveem.org 26 Apr 2019 + # Decompiled source of 0x06012c8cf97bead5deae237070f9587f8e7a266d + # + # Let's make the world open source + #  + + const name = 'CryptoKitties' + const symbol = 'CK' + const GEN0_STARTING_PRICE = 10^16 + const GEN0_AUCTION_DURATION = (24 * 3600) + const GEN0_CREATION_LIMIT = 45000 + const PROMO_CREATION_LIMIT = 5000 + + """ + + assert AddressDecompiledContractView.highlight_decompiled_code(code) == + "#\n# Eveem.org 26 Apr 2019\n# Decompiled source of 0x06012c8cf97bead5deae237070f9587f8e7a266d\n#\n# Let's make the world open source\n# \n\nconst name = 'CryptoKitties'\nconst symbol = 'CK'\nconst GEN0_STARTING_PRICE = 10^16\nconst GEN0_AUCTION_DURATION = (24 * 3600)\nconst GEN0_CREATION_LIMIT = 45000\nconst PROMO_CREATION_LIMIT = 5000\n\n\n\n" + end end describe "sort_contracts_by_version/1" do From ead42622a6299a4d5df13787ea48a3b27f5cdcbb Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Wed, 15 May 2019 15:55:39 +0300 Subject: [PATCH 40/68] add CHANGELOG entry --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 269c47625b..2873f391a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,8 @@ - [#1898](https://github.com/poanetwork/blockscout/pull/1898) - check if the constructor has arguments before verifying constructor arguments - [#1915](https://github.com/poanetwork/blockscout/pull/1915) - fallback to 2 latest evm versions - [#1937](https://github.com/poanetwork/blockscout/pull/1937) - Check the presence of overlap[i] object before retrieving properties from it +- [#1960](https://github.com/poanetwork/blockscout/pull/1960) - do not remove bold text in decompiled contacts #1960 + ### Chore From fa9c50853315ccb5d500247cd20ff98a923bf0f1 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Wed, 15 May 2019 16:31:52 +0300 Subject: [PATCH 41/68] cond instead fo if --- .dialyzer-ignore | 2 +- .../lib/block_scout_web/views/layout_view.ex | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/.dialyzer-ignore b/.dialyzer-ignore index e6b6ee70a7..53e3554c6e 100644 --- a/.dialyzer-ignore +++ b/.dialyzer-ignore @@ -4,4 +4,4 @@ apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex:400: Function timestamp_to_datetime/1 has no local return apps/explorer/lib/explorer/repo/prometheus_logger.ex:8: Function microseconds_time/1 has no local return apps/explorer/lib/explorer/repo/prometheus_logger.ex:8: The call 'Elixir.System':convert_time_unit(__@1::any(),'native','microseconds') breaks the contract (integer(),time_unit() | 'native',time_unit() | 'native') -> integer() -apps/block_scout_web/lib/block_scout_web/views/layout_view.ex:165: The call 'Elixir.Poison.Parser':'parse!'(any(),#{'keys':='atoms!'}) will never return since the success typing is (binary() | maybe_improper_list(binary() | maybe_improper_list(any(),binary() | []) | byte(),binary() | []),[{atom(),_}]) -> 'false' | 'nil' | 'true' | binary() | ['false' | 'nil' | 'true' | binary() | [any()] | number() | map()] | number() | map() and the contract is (iodata(),'Elixir.Keyword':t()) -> t() \ No newline at end of file +apps/block_scout_web/lib/block_scout_web/views/layout_view.ex:174: The call 'Elixir.Poison.Parser':'parse!'(any(),#{'keys':='atoms!'}) will never return since the success typing is (binary() | maybe_improper_list(binary() | maybe_improper_list(any(),binary() | []) | byte(),binary() | []),[{atom(),_}]) -> 'false' | 'nil' | 'true' | binary() | ['false' | 'nil' | 'true' | binary() | [any()] | number() | map()] | number() | map() and the contract is (iodata(),'Elixir.Keyword':t()) -> t() \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex b/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex index 2a216d13fd..12df6d3f0a 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex @@ -145,13 +145,22 @@ defmodule BlockScoutWeb.LayoutView do release_link_env_var = Application.get_env(:block_scout_web, :release_link) release_link = - if release_link_env_var == "" || release_link_env_var == nil do - "https://github.com/poanetwork/blockscout/releases/tag/" <> version - else - release_link_env_var + cond do + version == "" || version == nil -> + nil + + release_link_env_var == "" || release_link_env_var == nil -> + "https://github.com/poanetwork/blockscout/releases/tag/" <> version + + true -> + release_link_env_var end - html_escape({:safe, "#{version}"}) + if release_link == nil do + "" + else + html_escape({:safe, "#{version}"}) + end end def ignore_version?("unknown"), do: true From 40dae3a120d99f0651d1a4941850ce1d34b18697 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Wed, 15 May 2019 16:56:43 +0300 Subject: [PATCH 42/68] Extend and fix tests --- .../views/layout_view_test.exs | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/test/block_scout_web/views/layout_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/layout_view_test.exs index de693dac05..5787159b14 100644 --- a/apps/block_scout_web/test/block_scout_web/views/layout_view_test.exs +++ b/apps/block_scout_web/test/block_scout_web/views/layout_view_test.exs @@ -62,20 +62,32 @@ defmodule BlockScoutWeb.LayoutViewTest do end describe "release_link/1" do - test "use the version when there is no release_link env configured for it" do + test "set empty string if no blockscout version configured" do + Application.put_env(:block_scout_web, :blockscout_version, nil) + + assert LayoutView.release_link(nil) == "" + end + + test "set empty string if blockscout version is empty string" do + Application.put_env(:block_scout_web, :blockscout_version, "") + + assert LayoutView.release_link("") == "" + end + + test "use the default value when there is no release_link env configured for it" do Application.put_env(:block_scout_web, :release_link, nil) - assert LayoutView.release_link("1.3.4") == + assert LayoutView.release_link("v1.3.4-beta") == {:safe, - ~s(1.3.4)} + ~s(v1.3.4-beta)} end - test "use the version when empty release_link env configured for it" do + test "use the default value when empty release_link env configured for it" do Application.put_env(:block_scout_web, :release_link, "") - assert LayoutView.release_link("1.3.4") == + assert LayoutView.release_link("v1.3.4-beta") == {:safe, - ~s(1.3.4)} + ~s(v1.3.4-beta)} end test "use the enviroment release link when it's configured" do @@ -85,9 +97,9 @@ defmodule BlockScoutWeb.LayoutViewTest do "https://github.com/poanetwork/blockscout/releases/tag/v1.3.4-beta" ) - assert LayoutView.release_link("1.3.4") == + assert LayoutView.release_link("v1.3.4-beta") == {:safe, - ~s(1.3.4)} + ~s(v1.3.4-beta)} end end From d727ab9728372f15ab51eb5861034153f807a30f Mon Sep 17 00:00:00 2001 From: pasqu4le Date: Thu, 9 May 2019 12:32:02 +0200 Subject: [PATCH 43/68] Test block refetch after transactions overwritten by race condition Regression test for the race condition described in issue #1911 --- .../chain/import/runner/blocks_test.exs | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs b/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs index ecf1d71dfb..63110cba40 100644 --- a/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs +++ b/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs @@ -6,7 +6,7 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do import Explorer.Chain.Import.RunnerCase, only: [insert_address_with_token_balances: 1, update_holder_count!: 2] alias Ecto.Multi - alias Explorer.Chain.Import.Runner.{Blocks, Transaction} + alias Explorer.Chain.Import.Runner.{Blocks, Transactions} alias Explorer.Chain.{Address, Block, Transaction} alias Explorer.Chain alias Explorer.Repo @@ -283,6 +283,29 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do insert_block(new_block1, options) assert Chain.missing_block_number_ranges(range) == [] end + + # Regression test for https://github.com/poanetwork/blockscout/issues/1911 + test "forces block refetch if transaction is re-collated in a different block", + %{consensus_block: %Block{number: block_number, hash: block_hash, miner_hash: miner_hash}, options: options} do + 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) + + range = block_number..(block_number + 2) + + insert_block(new_block1, options) + insert_block(new_block2, options) + assert Chain.missing_block_number_ranges(range) == [] + + trans_hash = transaction_hash() + + transaction1 = transaction_params_with_block([hash: trans_hash], new_block1) + insert_transaction(transaction1, options) + assert Chain.missing_block_number_ranges(range) == [] + + transaction2 = transaction_params_with_block([hash: trans_hash], new_block2) + insert_transaction(transaction2, options) + assert Chain.missing_block_number_ranges(range) == [(block_number + 1)..(block_number + 1)] + end end defp insert_block(block_params, options) do @@ -293,6 +316,28 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do |> Repo.transaction() end + defp transaction_params_with_block(transaction_params, block_params) do + params_for(:transaction, transaction_params) + |> Map.merge(%{ + block_hash: block_params.hash, + block_number: block_params.number, + cumulative_gas_used: 50_000, + error: nil, + gas_used: 50_000, + index: 0, + from_address_hash: insert(:address).hash + }) + end + + defp insert_transaction(transaction_params, options) do + %Ecto.Changeset{valid?: true, changes: transaction_changes} = + Transaction.changeset(%Transaction{}, transaction_params) + + Multi.new() + |> Transactions.run([transaction_changes], options) + |> Repo.transaction() + end + defp count(schema) do Repo.one!(select(schema, fragment("COUNT(*)"))) end From e73b7276285b8b6fb9e39786e3e839a351336ad6 Mon Sep 17 00:00:00 2001 From: goodsoft Date: Wed, 8 May 2019 18:43:53 +0300 Subject: [PATCH 44/68] Fix potential race condition while dropping replaced transactions Don't discard a transaction if it was eventually collated in the period between replaced transaction worker initialization and actual dropping. --- 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 adb9759638..59258a90d1 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -2565,7 +2565,7 @@ defmodule Explorer.Chain do join: duplicate in subquery(query), on: duplicate.nonce == pending.nonce, on: duplicate.from_address_hash == pending.from_address_hash, - where: pending.hash in ^hashes + where: pending.hash in ^hashes and is_nil(pending.block_hash) ) Repo.update_all(transactions_to_update, [set: [error: "dropped/replaced", status: :error]], timeout: timeout) From b9cc8a417fdf22eb6e3d35643fd2eb2aa4237fb9 Mon Sep 17 00:00:00 2001 From: goodsoft Date: Wed, 8 May 2019 18:48:09 +0300 Subject: [PATCH 45/68] Force block refetch if transaction is recollated in a different block Due to race conditions described in #1911 transactions from a consensus block might get overwritten by the same transactions from a non-consensus block. To prevent this we force a block refetch (by marking it as non-consensus), if a transaction belonging to it gets overwritten by the same transaction from a different block. --- .../chain/import/runner/transactions.ex | 50 +++++++++++++++++-- .../lib/explorer/chain/transaction.ex | 5 ++ ...22_add_old_block_hash_for_transactions.exs | 12 +++++ 3 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 apps/explorer/priv/repo/migrations/20190508152922_add_old_block_hash_for_transactions.exs diff --git a/apps/explorer/lib/explorer/chain/import/runner/transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/transactions.ex index 4f1d2d6fe5..1a9fcc497d 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/transactions.ex @@ -8,7 +8,7 @@ defmodule Explorer.Chain.Import.Runner.Transactions do import Ecto.Query, only: [from: 2] alias Ecto.{Multi, Repo} - alias Explorer.Chain.{Data, Hash, Import, Transaction} + alias Explorer.Chain.{Block, Data, Hash, Import, Transaction} alias Explorer.Chain.Import.Runner.TokenTransfers @behaviour Import.Runner @@ -42,9 +42,13 @@ defmodule Explorer.Chain.Import.Runner.Transactions do |> Map.put(:timestamps, timestamps) |> Map.put(:token_transfer_transaction_hash_set, token_transfer_transaction_hash_set(options)) - Multi.run(multi, :transactions, fn repo, _ -> + multi + |> Multi.run(:transactions, fn repo, _ -> insert(repo, changes_list, insert_options) end) + |> Multi.run(:recollated_transactions, fn repo, %{transactions: transactions} -> + discard_blocks_for_recollated_transactions(repo, transactions, insert_options) + end) end @impl Import.Runner @@ -87,7 +91,7 @@ defmodule Explorer.Chain.Import.Runner.Transactions do on_conflict: on_conflict, for: Transaction, returning: - ~w(block_number index hash internal_transactions_indexed_at block_hash nonce from_address_hash created_contract_address_hash)a, + ~w(block_number index hash internal_transactions_indexed_at block_hash old_block_hash nonce from_address_hash created_contract_address_hash)a, timeout: timeout, timestamps: timestamps ) @@ -99,6 +103,7 @@ defmodule Explorer.Chain.Import.Runner.Transactions do update: [ set: [ block_hash: fragment("EXCLUDED.block_hash"), + old_block_hash: transaction.block_hash, block_number: fragment("EXCLUDED.block_number"), created_contract_address_hash: fragment("EXCLUDED.created_contract_address_hash"), cumulative_gas_used: fragment("EXCLUDED.cumulative_gas_used"), @@ -179,4 +184,43 @@ defmodule Explorer.Chain.Import.Runner.Transactions do end defp put_internal_transactions_indexed_at?(_, _), do: false + + defp discard_blocks_for_recollated_transactions(repo, transactions, %{ + timeout: timeout, + timestamps: %{updated_at: updated_at} + }) + when is_list(transactions) do + ordered_block_hashes = + transactions + |> Enum.filter(fn %{block_hash: block_hash, old_block_hash: old_block_hash} -> + not is_nil(old_block_hash) and block_hash != old_block_hash + end) + |> MapSet.new(& &1.old_block_hash) + |> Enum.sort() + + if Enum.empty?(ordered_block_hashes) do + {:ok, []} + else + query = + from( + block in Block, + where: block.hash in ^ordered_block_hashes, + update: [ + set: [ + consensus: false, + updated_at: ^updated_at + ] + ] + ) + + try do + {_, result} = repo.update_all(query, [], timeout: timeout) + + {:ok, result} + rescue + postgrex_error in Postgrex.Error -> + {:error, %{exception: postgrex_error, block_hashes: ordered_block_hashes}} + end + end + end end diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index 1cf9c8f116..de99198181 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -205,6 +205,11 @@ defmodule Explorer.Chain.Transaction do field(:v, :decimal) field(:value, Wei) + # A transient field for deriving old block hash during transaction upserts. + # Used to force refetch of a block in case a transaction is re-collated + # in a different block. See: https://github.com/poanetwork/blockscout/issues/1911 + field(:old_block_hash, Hash.Full) + timestamps() belongs_to(:block, Block, foreign_key: :block_hash, references: :hash, type: Hash.Full) diff --git a/apps/explorer/priv/repo/migrations/20190508152922_add_old_block_hash_for_transactions.exs b/apps/explorer/priv/repo/migrations/20190508152922_add_old_block_hash_for_transactions.exs new file mode 100644 index 0000000000..daf04c22f3 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20190508152922_add_old_block_hash_for_transactions.exs @@ -0,0 +1,12 @@ +defmodule Explorer.Repo.Migrations.AddOldBlockHashForTransactions do + use Ecto.Migration + + def change do + alter table(:transactions) do + # A transient field for deriving old block hash during transaction upserts. + # Used to force refetch of a block in case a transaction is re-collated + # in a different block. See: https://github.com/poanetwork/blockscout/issues/1911 + add(:old_block_hash, :bytea, null: true) + end + end +end From 64acef7b7106ab008355fe649ef348211e4c9231 Mon Sep 17 00:00:00 2001 From: pasqu4le Date: Fri, 10 May 2019 19:52:22 +0200 Subject: [PATCH 46/68] Add temporary indexer worker to remove consensus on blocks with mismatching transactions After the solution for #1991 it is necessary to remove consensus from blocks whose number of transactions mismatch compared to a node, so that these blocks are refetched. --- CHANGELOG.md | 1 + apps/explorer/lib/explorer/chain/block.ex | 6 +- ...0513134025_add_refetch_needed_to_block.exs | 11 ++ apps/indexer/README.md | 1 + apps/indexer/lib/indexer/supervisor.ex | 3 + .../temporary/blocks_transactions_mismatch.ex | 115 ++++++++++++++++++ 6 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 apps/explorer/priv/repo/migrations/20190513134025_add_refetch_needed_to_block.exs create mode 100644 apps/indexer/lib/indexer/temporary/blocks_transactions_mismatch.ex diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a7159ec7d..22cca8a536 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ - [#1898](https://github.com/poanetwork/blockscout/pull/1898) - check if the constructor has arguments before verifying constructor arguments - [#1915](https://github.com/poanetwork/blockscout/pull/1915) - fallback to 2 latest evm versions - [#1937](https://github.com/poanetwork/blockscout/pull/1937) - Check the presence of overlap[i] object before retrieving properties from it +- [#1917](https://github.com/poanetwork/blockscout/pull/1917) - Force block refetch if transaction is re-collated in a different block ### Chore diff --git a/apps/explorer/lib/explorer/chain/block.ex b/apps/explorer/lib/explorer/chain/block.ex index 58d187d9d8..9cf86b570b 100644 --- a/apps/explorer/lib/explorer/chain/block.ex +++ b/apps/explorer/lib/explorer/chain/block.ex @@ -10,7 +10,7 @@ defmodule Explorer.Chain.Block do alias Explorer.Chain.{Address, Gas, Hash, Transaction} alias Explorer.Chain.Block.{Reward, SecondDegreeRelation} - @optional_attrs ~w(internal_transactions_indexed_at size)a + @optional_attrs ~w(internal_transactions_indexed_at size refetch_needed)a @required_attrs ~w(consensus difficulty gas_limit gas_used hash miner_hash nonce number parent_hash timestamp total_difficulty)a @@ -63,7 +63,8 @@ defmodule Explorer.Chain.Block do timestamp: DateTime.t(), total_difficulty: difficulty(), transactions: %Ecto.Association.NotLoaded{} | [Transaction.t()], - internal_transactions_indexed_at: DateTime.t() + internal_transactions_indexed_at: DateTime.t(), + refetch_needed: boolean() } @primary_key {:hash, Hash.Full, autogenerate: false} @@ -78,6 +79,7 @@ defmodule Explorer.Chain.Block do field(:timestamp, :utc_datetime_usec) field(:total_difficulty, :decimal) field(:internal_transactions_indexed_at, :utc_datetime_usec) + field(:refetch_needed, :boolean) timestamps() diff --git a/apps/explorer/priv/repo/migrations/20190513134025_add_refetch_needed_to_block.exs b/apps/explorer/priv/repo/migrations/20190513134025_add_refetch_needed_to_block.exs new file mode 100644 index 0000000000..70ddac3e03 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20190513134025_add_refetch_needed_to_block.exs @@ -0,0 +1,11 @@ +defmodule Explorer.Repo.Migrations.AddRefetchNeededToBlock do + use Ecto.Migration + + def change do + alter table(:blocks) do + add(:refetch_needed, :boolean, default: false) + end + + execute("UPDATE blocks SET refetch_needed = TRUE;", "") + end +end diff --git a/apps/indexer/README.md b/apps/indexer/README.md index 34fb6e6ef3..173df0c6ab 100644 --- a/apps/indexer/README.md +++ b/apps/indexer/README.md @@ -92,6 +92,7 @@ After all deployed instances get all needed data, these fetchers should be depre - `uncataloged_token_transfers`: extracts token transfers from logs, which previously weren't parsed due to unknown format - `uncles_without_index`: adds previously unfetched `index` field for unfetched blocks in `block_second_degree_relations` +- `blocks_transactions_mismatch`: refetches each block once and revokes consensus to those whose transaction number mismatches with the number currently stored. This is meant to force the correction of a race condition that caused successfully fetched transactions to be overwritten by a following non-consensus block: [#1911](https://github.com/poanetwork/blockscout/issues/1911). ## Memory Usage diff --git a/apps/indexer/lib/indexer/supervisor.ex b/apps/indexer/lib/indexer/supervisor.ex index 21dc3637ec..b2c3d19664 100644 --- a/apps/indexer/lib/indexer/supervisor.ex +++ b/apps/indexer/lib/indexer/supervisor.ex @@ -24,6 +24,7 @@ defmodule Indexer.Supervisor do } alias Indexer.Temporary.{ + BlocksTransactionsMismatch, UncatalogedTokenTransfers, UnclesWithoutIndex } @@ -124,6 +125,8 @@ defmodule Indexer.Supervisor do # Temporary workers {UncatalogedTokenTransfers.Supervisor, [[]]}, {UnclesWithoutIndex.Supervisor, + [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, + {BlocksTransactionsMismatch.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]} ], strategy: :one_for_one diff --git a/apps/indexer/lib/indexer/temporary/blocks_transactions_mismatch.ex b/apps/indexer/lib/indexer/temporary/blocks_transactions_mismatch.ex new file mode 100644 index 0000000000..76f4322379 --- /dev/null +++ b/apps/indexer/lib/indexer/temporary/blocks_transactions_mismatch.ex @@ -0,0 +1,115 @@ +defmodule Indexer.Temporary.BlocksTransactionsMismatch do + @moduledoc """ + Fetches `consensus` `t:Explorer.Chain.Block.t/0` and compares their transaction + number against a node, to revoke `consensus` on mismatch. + + This is meant to fix incorrectly strored transactions that happened as a result + of a race condition due to the asynchronicity of indexer's components. + """ + + use Indexer.Fetcher + + require Logger + + import Ecto.Query + + alias Ecto.Multi + alias EthereumJSONRPC.Blocks + alias Explorer.Chain.Block + alias Explorer.Repo + alias Indexer.BufferedTask + + @behaviour BufferedTask + + @defaults [ + flush_interval: :timer.seconds(3), + max_batch_size: 10, + max_concurrency: 4, + task_supervisor: Indexer.Temporary.BlocksTransactionsMismatch.TaskSupervisor, + metadata: [fetcher: :blocks_transactions_mismatch] + ] + + @doc false + def child_spec([init_options, gen_server_options]) when is_list(init_options) do + {state, mergeable_init_options} = Keyword.pop(init_options, :json_rpc_named_arguments) + + unless state do + raise ArgumentError, + ":json_rpc_named_arguments must be provided to `#{__MODULE__}.child_spec " <> + "to allow for json_rpc calls when running." + end + + merged_init_options = + @defaults + |> Keyword.merge(mergeable_init_options) + |> Keyword.put(:state, state) + + Supervisor.child_spec({BufferedTask, [{__MODULE__, merged_init_options}, gen_server_options]}, id: __MODULE__) + end + + @impl BufferedTask + def init(initial, reducer, _) do + query = + from(block in Block, + join: transactions in assoc(block, :transactions), + where: block.consensus and block.refetch_needed, + group_by: block.hash, + select: {block, count(transactions.hash)} + ) + + {:ok, final} = Repo.stream_reduce(query, initial, &reducer.(&1, &2)) + + final + end + + @impl BufferedTask + def run(blocks_data, json_rpc_named_arguments) do + hashes = Enum.map(blocks_data, fn {block, _trans_num} -> block.hash end) + + Logger.debug("fetching") + + case EthereumJSONRPC.fetch_blocks_by_hash(hashes, json_rpc_named_arguments) do + {:ok, blocks} -> + run_blocks(blocks, blocks_data) + + {:error, reason} -> + Logger.error(fn -> ["failed to fetch: ", inspect(reason)] end) + {:retry, blocks_data} + end + end + + defp run_blocks(%Blocks{blocks_params: []}, blocks_data), do: {:retry, blocks_data} + + defp run_blocks( + %Blocks{transactions_params: transactions_params}, + blocks_data + ) do + found_blocks_map = + transactions_params + |> Enum.group_by(&Map.fetch!(&1, :block_hash)) + |> Map.new(fn {block_hash, trans_lst} -> {block_hash, Enum.count(trans_lst)} end) + + {found_blocks_data, missing_blocks_data} = + Enum.split_with(blocks_data, fn {block, _trans_num} -> + Map.has_key?(found_blocks_map, to_string(block.hash)) + end) + + {:ok, _} = + found_blocks_data + |> Enum.reduce(Multi.new(), fn {block, trans_num}, multi -> + changes = %{ + refetch_needed: false, + consensus: found_blocks_map[to_string(block.hash)] == trans_num + } + + Multi.update(multi, block.hash, Block.changeset(block, changes)) + end) + |> Repo.transaction() + + if Enum.empty?(missing_blocks_data) do + :ok + else + {:retry, missing_blocks_data} + end + end +end From 750a665fe6ef35ac44e35e90d61c3ce353f6776b Mon Sep 17 00:00:00 2001 From: Gabriel Rodriguez Alsina Date: Wed, 15 May 2019 12:10:28 -0300 Subject: [PATCH 47/68] (fix) remove text --- .../templates/address_token_balance/_tokens.html.eex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_tokens.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_tokens.html.eex index 13a8b56062..1366a83a0a 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_tokens.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_tokens.html.eex @@ -15,7 +15,7 @@ class: "dropdown-item" ) do %>
-

xxxxxxxxxxxxx<%= token_name(token_balance.token) %>

+

<%= token_name(token_balance.token) %>

<%= if token_balance.token.usd_value do %>

@@ -23,7 +23,7 @@ <% end %>

-

xxxxxxxxxxx +

<%= format_according_to_decimals(token_balance.value, token_balance.token.decimals) %> <%= token_balance.token.symbol %>

<%= if token_balance.token.usd_value do %> From 1a9565c862a1896b116da90a535b9d565f005e43 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Wed, 15 May 2019 18:10:58 +0300 Subject: [PATCH 48/68] remove PR number from CHANGELOG entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2873f391a6..d0ae200a69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,7 +42,7 @@ - [#1898](https://github.com/poanetwork/blockscout/pull/1898) - check if the constructor has arguments before verifying constructor arguments - [#1915](https://github.com/poanetwork/blockscout/pull/1915) - fallback to 2 latest evm versions - [#1937](https://github.com/poanetwork/blockscout/pull/1937) - Check the presence of overlap[i] object before retrieving properties from it -- [#1960](https://github.com/poanetwork/blockscout/pull/1960) - do not remove bold text in decompiled contacts #1960 +- [#1960](https://github.com/poanetwork/blockscout/pull/1960) - do not remove bold text in decompiled contacts ### Chore From caf4e6de78a7b685376fc983036d3c3484543c6b Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 16 May 2019 09:57:02 +0300 Subject: [PATCH 49/68] remove period based update --- .../lib/explorer/chain/block_number_cache.ex | 46 +++------------ .../chain/block_number_cache_test.exs | 56 +++++-------------- apps/explorer/test/support/data_case.ex | 2 +- 3 files changed, 22 insertions(+), 82 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/block_number_cache.ex b/apps/explorer/lib/explorer/chain/block_number_cache.ex index d15d6781e6..b74e0999bf 100644 --- a/apps/explorer/lib/explorer/chain/block_number_cache.ex +++ b/apps/explorer/lib/explorer/chain/block_number_cache.ex @@ -6,13 +6,10 @@ defmodule Explorer.Chain.BlockNumberCache do alias Explorer.Chain @tab :block_number_cache - # 30 minutes - @cache_period 1_000 * 60 * 30 @key "min_max" - @opts_key "opts" - @spec setup(Keyword.t()) :: :ok - def setup(opts \\ []) do + @spec setup() :: :ok + def setup do if :ets.whereis(@tab) == :undefined do :ets.new(@tab, [ :set, @@ -22,7 +19,6 @@ defmodule Explorer.Chain.BlockNumberCache do ]) end - setup_opts(opts) update_cache() :ok @@ -41,16 +37,7 @@ defmodule Explorer.Chain.BlockNumberCache do end defp value(type) do - initial_cache = {_min, _max, old_current_time} = cached_values() - - {min, max, _current_time} = - if current_time() - old_current_time > cache_period() do - update_cache() - - cached_values() - else - initial_cache - end + {min, max} = cached_values() case type do :max -> max @@ -61,15 +48,15 @@ defmodule Explorer.Chain.BlockNumberCache do @spec update(non_neg_integer()) :: boolean() def update(number) do - {old_min, old_max, time} = cached_values() + {old_min, old_max} = cached_values() cond do number > old_max -> - tuple = {old_min, number, time} + tuple = {old_min, number} :ets.insert(@tab, {@key, tuple}) number < old_min -> - tuple = {number, old_max, time} + tuple = {number, old_max} :ets.insert(@tab, {@key, tuple}) true -> @@ -78,41 +65,22 @@ defmodule Explorer.Chain.BlockNumberCache do end defp update_cache do - current_time = current_time() {min, max} = min_and_max_from_db() - tuple = {min, max, current_time} + tuple = {min, max} :ets.insert(@tab, {@key, tuple}) end - defp setup_opts(opts) do - cache_period = opts[:cache_period] || @cache_period - - :ets.insert(@tab, {@opts_key, cache_period}) - end - defp cached_values do [{_, cached_values}] = :ets.lookup(@tab, @key) cached_values end - defp cache_period do - [{_, cache_period}] = :ets.lookup(@tab, @opts_key) - - cache_period - end - defp min_and_max_from_db do Chain.fetch_min_and_max_block_numbers() rescue _e -> {0, 0} end - - defp current_time do - utc_now = DateTime.utc_now() - - DateTime.to_unix(utc_now, :millisecond) - end end diff --git a/apps/explorer/test/explorer/chain/block_number_cache_test.exs b/apps/explorer/test/explorer/chain/block_number_cache_test.exs index a33263293c..870b1b4240 100644 --- a/apps/explorer/test/explorer/chain/block_number_cache_test.exs +++ b/apps/explorer/test/explorer/chain/block_number_cache_test.exs @@ -11,33 +11,6 @@ defmodule Explorer.Chain.BlockNumberCacheTest do assert BlockNumberCache.max_number() == 5 end - - test "invalidates cache if period did pass" do - insert(:block, number: 5) - - BlockNumberCache.setup(cache_period: 2_000) - - assert BlockNumberCache.max_number() == 5 - - insert(:block, number: 10) - - Process.sleep(2_000) - - assert BlockNumberCache.max_number() == 10 - assert BlockNumberCache.min_number() == 5 - end - - test "does not invalidate cache if period time did not pass" do - insert(:block, number: 5) - - BlockNumberCache.setup(cache_period: 10_000) - - assert BlockNumberCache.max_number() == 5 - - insert(:block, number: 10) - - assert BlockNumberCache.max_number() == 5 - end end describe "min_number/1" do @@ -48,32 +21,31 @@ defmodule Explorer.Chain.BlockNumberCacheTest do assert BlockNumberCache.max_number() == 2 end + end - test "invalidates cache" do - insert(:block, number: 5) - - BlockNumberCache.setup(cache_period: 2_000) + describe "update/1" do + test "updates max number" do + insert(:block, number: 2) - assert BlockNumberCache.min_number() == 5 + BlockNumberCache.setup() - insert(:block, number: 2) + assert BlockNumberCache.max_number() == 2 - Process.sleep(2_000) + assert BlockNumberCache.update(3) - assert BlockNumberCache.min_number() == 2 - assert BlockNumberCache.max_number() == 5 + assert BlockNumberCache.max_number() == 3 end - test "does not invalidate cache if period time did not pass" do - insert(:block, number: 5) + test "updates min number" do + insert(:block, number: 2) - BlockNumberCache.setup(cache_period: 10_000) + BlockNumberCache.setup() - assert BlockNumberCache.max_number() == 5 + assert BlockNumberCache.min_number() == 2 - insert(:block, number: 2) + assert BlockNumberCache.update(1) - assert BlockNumberCache.max_number() == 5 + assert BlockNumberCache.min_number() == 1 end end end diff --git a/apps/explorer/test/support/data_case.ex b/apps/explorer/test/support/data_case.ex index 75d3429e70..68bb21434c 100644 --- a/apps/explorer/test/support/data_case.ex +++ b/apps/explorer/test/support/data_case.ex @@ -39,7 +39,7 @@ defmodule Explorer.DataCase do Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, {:shared, self()}) end - Explorer.Chain.BlockNumberCache.setup(cache_period: 0) + Explorer.Chain.BlockNumberCache.setup() :ok end From 390bb24d643b0f0e46f109d775af89d55a9b43f9 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 16 May 2019 10:37:28 +0300 Subject: [PATCH 50/68] fix tests --- apps/explorer/config/config.exs | 2 ++ apps/explorer/config/test.exs | 2 ++ apps/explorer/lib/explorer/chain/block_number_cache.ex | 7 ++++++- .../test/explorer/chain/block_number_cache_test.exs | 4 ++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index e6eb5c4813..eba5dd34cf 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -13,6 +13,8 @@ config :explorer, config :explorer, Explorer.Counters.AverageBlockTime, enabled: true +config :explorer, Explorer.Chain.BlockNumberCache, enabled: true + config :explorer, Explorer.ExchangeRates.Source.CoinMarketCap, pages: String.to_integer(System.get_env("COINMARKETCAP_PAGES") || "10") diff --git a/apps/explorer/config/test.exs b/apps/explorer/config/test.exs index 0a717d8b27..f27fc3b350 100644 --- a/apps/explorer/config/test.exs +++ b/apps/explorer/config/test.exs @@ -13,6 +13,8 @@ config :explorer, Explorer.Repo, config :explorer, Explorer.ExchangeRates, enabled: false, store: :ets +config :explorer, Explorer.Chain.BlockNumberCache, enabled: false + config :explorer, Explorer.KnownTokens, enabled: false, store: :ets config :explorer, Explorer.Counters.AverageBlockTime, enabled: false diff --git a/apps/explorer/lib/explorer/chain/block_number_cache.ex b/apps/explorer/lib/explorer/chain/block_number_cache.ex index b74e0999bf..2f335c7a2c 100644 --- a/apps/explorer/lib/explorer/chain/block_number_cache.ex +++ b/apps/explorer/lib/explorer/chain/block_number_cache.ex @@ -37,7 +37,12 @@ defmodule Explorer.Chain.BlockNumberCache do end defp value(type) do - {min, max} = cached_values() + {min, max} = + if Application.get_env(:explorer, __MODULE__)[:enabled] do + cached_values() + else + min_and_max_from_db() + end case type do :max -> max diff --git a/apps/explorer/test/explorer/chain/block_number_cache_test.exs b/apps/explorer/test/explorer/chain/block_number_cache_test.exs index 870b1b4240..71f1aa8433 100644 --- a/apps/explorer/test/explorer/chain/block_number_cache_test.exs +++ b/apps/explorer/test/explorer/chain/block_number_cache_test.exs @@ -3,6 +3,10 @@ defmodule Explorer.Chain.BlockNumberCacheTest do alias Explorer.Chain.BlockNumberCache + setup do + Application.put_env(:explorer, Explorer.Chain.BlockNumberCache, enabled: true) + end + describe "max_number/1" do test "returns max number" do insert(:block, number: 5) From 9b576680981778d56fe43bd53d14219715675953 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 16 May 2019 11:08:47 +0300 Subject: [PATCH 51/68] fix indexer tests --- .../controllers/api/rpc/address_controller_test.exs | 1 - apps/explorer/test/explorer/chain/block_number_cache_test.exs | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) 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 b3b8f0b08b..993b25fcec 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 @@ -25,7 +25,6 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do start_supervised!(AddressesWithBalanceCounter) Application.put_env(:explorer, AverageBlockTime, enabled: true) - BlockNumberCache.setup(cache_period: 0) on_exit(fn -> Application.put_env(:explorer, AverageBlockTime, enabled: false) diff --git a/apps/explorer/test/explorer/chain/block_number_cache_test.exs b/apps/explorer/test/explorer/chain/block_number_cache_test.exs index 71f1aa8433..7b501a718b 100644 --- a/apps/explorer/test/explorer/chain/block_number_cache_test.exs +++ b/apps/explorer/test/explorer/chain/block_number_cache_test.exs @@ -5,6 +5,10 @@ defmodule Explorer.Chain.BlockNumberCacheTest do setup do Application.put_env(:explorer, Explorer.Chain.BlockNumberCache, enabled: true) + + on_exit(fn -> + Application.put_env(:explorer, Explorer.Chain.BlockNumberCache, enabled: false) + end) end describe "max_number/1" do From 0dc13697a67ab5114e4dfe2144335115fb7e6cbd Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 16 May 2019 12:19:31 +0300 Subject: [PATCH 52/68] fix pagination --- apps/explorer/lib/explorer/chain.ex | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 22d0a0cd6a..4fa6460f16 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -287,15 +287,14 @@ defmodule Explorer.Chain do options \\ [] ) when is_list(options) do - paging_options = Keyword.get(options, :paging_options) + paging_options = Keyword.get(options, :paging_options) || %PagingOptions{page_size: 50} {block_number, transaction_index, log_index} = paging_options.key || {BlockNumberCache.max_number(), 0, 0} query = - from(transaction in Transaction, - inner_join: log in assoc(transaction, :logs), - order_by: [desc: transaction.block_number, desc: transaction.index], - preload: [:logs], + from(log in Log, + inner_join: transaction in assoc(log, :transaction), + order_by: [desc: transaction.block_number, desc: transaction.index, asc: log.index], where: (transaction.from_address_hash == ^address_hash or transaction.to_address_hash == ^address_hash or transaction.created_contract_address_hash == ^address_hash) and log.address_hash == ^address_hash and @@ -303,13 +302,11 @@ defmodule Explorer.Chain do (transaction.block_number == ^block_number and transaction.index > ^transaction_index) or (transaction.block_number == ^block_number and transaction.index == ^transaction_index and log.index > ^log_index)), - limit: ^paging_options.page_size, - distinct: transaction.hash + limit: ^paging_options.page_size ) query |> Repo.all() - |> Enum.flat_map(fn transaction -> transaction.logs end) |> Enum.take(paging_options.page_size) end From 5a63a9a97acf83acba9321bef408a94593006483 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 16 May 2019 12:23:32 +0300 Subject: [PATCH 53/68] fix response --- .../block_scout_web/views/api/rpc/eth_rpc_view.ex | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/eth_rpc_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/eth_rpc_view.ex index e39cedf3a5..39eb5ae9d1 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/eth_rpc_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/eth_rpc_view.ex @@ -20,21 +20,13 @@ defmodule BlockScoutWeb.API.RPC.EthRPCView do defimpl Poison.Encoder, for: BlockScoutWeb.API.RPC.EthRPCView do def encode(%BlockScoutWeb.API.RPC.EthRPCView{result: result, id: id, error: error}, _options) when is_nil(error) do """ - { - "jsonrpc": "2.0", - "result": "#{result}", - "id": #{id} - }\n + {"jsonrpc":"2.0","result":"#{result}","id":#{id}} """ end def encode(%BlockScoutWeb.API.RPC.EthRPCView{id: id, error: error}, _options) do """ - { - "jsonrpc": "2.0", - "error": #{error}, - "id": #{id} - }\n + {"jsonrpc":"2.0","error": #{error},"id": #{id}} """ end end From 0bebafb4ae6ec3a9e201b8200d185186ca7edae2 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 16 May 2019 14:45:26 +0300 Subject: [PATCH 54/68] add address logs view --- apps/block_scout_web/lib/block_scout_web/chain.ex | 5 +++++ apps/block_scout_web/lib/block_scout_web/router.ex | 7 +++++++ .../lib/block_scout_web/templates/address/_tabs.html.eex | 7 ++++++- .../lib/block_scout_web/views/address_view.ex | 1 + apps/explorer/lib/explorer/chain.ex | 4 +++- apps/explorer/test/explorer/chain_test.exs | 4 ++-- 6 files changed, 24 insertions(+), 4 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/chain.ex b/apps/block_scout_web/lib/block_scout_web/chain.ex index cabac4ae02..6412df44b2 100644 --- a/apps/block_scout_web/lib/block_scout_web/chain.ex +++ b/apps/block_scout_web/lib/block_scout_web/chain.ex @@ -209,6 +209,11 @@ defmodule BlockScoutWeb.Chain do %{"index" => index} end + defp paging_params({block_number, transaction_index, %Log{index: index}}) + when is_integer(block_number) and is_integer(transaction_index) do + %{"block_number" => block_number, "transaction_index" => transaction_index, "index" => index} + end + defp paging_params(%Transaction{block_number: nil, inserted_at: inserted_at, hash: hash}) do %{"inserted_at" => DateTime.to_iso8601(inserted_at), "hash" => hash} end diff --git a/apps/block_scout_web/lib/block_scout_web/router.ex b/apps/block_scout_web/lib/block_scout_web/router.ex index c0e975793f..b2f639d763 100644 --- a/apps/block_scout_web/lib/block_scout_web/router.ex +++ b/apps/block_scout_web/lib/block_scout_web/router.ex @@ -147,6 +147,13 @@ defmodule BlockScoutWeb.Router do as: :decompiled_contract ) + resources( + "/logs", + AddressLogsController, + only: [:index], + as: :logs + ) + resources( "/contract_verifications", AddressContractVerificationController, diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_tabs.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_tabs.html.eex index bbf27c8f96..8ed8d4e028 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address/_tabs.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_tabs.html.eex @@ -22,6 +22,11 @@ "data-test": "coin_balance_tab_link", to: address_coin_balance_path(@conn, :index, @address.hash) ) %> + <%= link( + gettext("Logs"), + class: "card-tab #{tab_status("logs", @conn.request_path)}", + to: address_logs_path(@conn, :index, @address.hash) + ) %> <%= if BlockScoutWeb.AddressView.validator?(@validation_count) do %> <%= link( gettext("Blocks Validated"), @@ -55,4 +60,4 @@ class: "card-tab #{tab_status("read_contract", @conn.request_path)}") %> <% end %> -
\ No newline at end of file +
diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex index 6ce72b90ae..7a6005d526 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex @@ -300,6 +300,7 @@ defmodule BlockScoutWeb.AddressView do defp tab_name(["read_contract"]), do: gettext("Read Contract") defp tab_name(["coin_balances"]), do: gettext("Coin Balance History") defp tab_name(["validations"]), do: gettext("Blocks Validated") + defp tab_name(["logs"]), do: gettext("Logs") def short_hash(%Address{hash: hash}) do << diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 4fa6460f16..4688206cc1 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -295,6 +295,7 @@ defmodule Explorer.Chain do from(log in Log, inner_join: transaction in assoc(log, :transaction), order_by: [desc: transaction.block_number, desc: transaction.index, asc: log.index], + preload: [:transaction], where: (transaction.from_address_hash == ^address_hash or transaction.to_address_hash == ^address_hash or transaction.created_contract_address_hash == ^address_hash) and log.address_hash == ^address_hash and @@ -302,7 +303,8 @@ defmodule Explorer.Chain do (transaction.block_number == ^block_number and transaction.index > ^transaction_index) or (transaction.block_number == ^block_number and transaction.index == ^transaction_index and log.index > ^log_index)), - limit: ^paging_options.page_size + limit: ^paging_options.page_size, + select: {transaction.block_number, transaction.index, log} ) query diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 201090112b..baf641403b 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -88,13 +88,13 @@ defmodule Explorer.ChainTest do paging_options1 = %PagingOptions{page_size: 1} - [log] = Chain.address_to_logs(address, paging_options: paging_options1) + [{_, _, log}] = Chain.address_to_logs(address, paging_options: paging_options1) assert log.index == log1.index paging_options2 = %PagingOptions{page_size: 60, key: {transaction.block_number, transaction.index, log1.index}} returned_log_indexes = - Chain.address_to_logs(address, paging_options: paging_options2) |> Enum.map(fn log -> log.index end) + Chain.address_to_logs(address, paging_options: paging_options2) |> Enum.map(fn {_, _, log} -> log.index end) assert second_page_indexes == returned_log_indexes end From d85c1abb568fd955c4a084147c0a430a9e4b218b Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 16 May 2019 14:54:03 +0300 Subject: [PATCH 55/68] fix gettext --- apps/block_scout_web/priv/gettext/default.pot | 19 +++++++++++---- .../priv/gettext/en/LC_MESSAGES/default.po | 23 ++++++++++++++----- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index 7922c7d2f0..4917896dd7 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -187,7 +187,7 @@ msgid "Blocks Indexed" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/_tabs.html.eex:27 +#: lib/block_scout_web/templates/address/_tabs.html.eex:32 #: lib/block_scout_web/templates/address/overview.html.eex:95 #: lib/block_scout_web/templates/address_validation/index.html.eex:13 #: lib/block_scout_web/views/address_view.ex:302 @@ -215,7 +215,7 @@ msgid "Close" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/_tabs.html.eex:37 +#: lib/block_scout_web/templates/address/_tabs.html.eex:42 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:165 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187 #: lib/block_scout_web/views/address_view.ex:298 @@ -334,6 +334,7 @@ msgid "Curl" msgstr "" #, elixir-format +#: lib/block_scout_web/templates/address_logs/index.html.eex:63 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:18 #: lib/block_scout_web/templates/transaction_log/index.html.eex:67 #: lib/block_scout_web/templates/transaction_log/index.html.eex:133 @@ -505,8 +506,11 @@ msgid "Limit" msgstr "" #, elixir-format +#: lib/block_scout_web/templates/address/_tabs.html.eex:26 +#: lib/block_scout_web/templates/address_logs/index.html.eex:8 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:17 #: lib/block_scout_web/templates/transaction_log/index.html.eex:8 +#: lib/block_scout_web/views/address_view.ex:303 #: lib/block_scout_web/views/transaction_view.ex:340 msgid "Logs" msgstr "" @@ -682,7 +686,7 @@ msgid "Query" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/_tabs.html.eex:53 +#: lib/block_scout_web/templates/address/_tabs.html.eex:58 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:25 #: lib/block_scout_web/views/address_view.ex:300 #: lib/block_scout_web/views/tokens/overview_view.ex:37 @@ -874,6 +878,7 @@ msgid "Top Accounts - %{subnetwork} Explorer" msgstr "" #, elixir-format +#: lib/block_scout_web/templates/address_logs/index.html.eex:33 #: lib/block_scout_web/templates/transaction_log/index.html.eex:103 msgid "Topics" msgstr "" @@ -894,6 +899,7 @@ msgid "Total transactions" msgstr "" #, elixir-format +#: lib/block_scout_web/templates/address_logs/index.html.eex:22 #: lib/block_scout_web/views/transaction_view.ex:287 msgid "Transaction" msgstr "" @@ -1661,7 +1667,7 @@ msgid "Decompiled Code" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/_tabs.html.eex:47 +#: lib/block_scout_web/templates/address/_tabs.html.eex:52 msgid "Decompiled code" msgstr "" @@ -1745,3 +1751,8 @@ msgstr "" #: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:37 msgid "of" msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_logs/index.html.eex:77 +msgid "There are no logs for this address." +msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index 8925a95acf..6fc4e2ddec 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -187,7 +187,7 @@ msgid "Blocks Indexed" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/_tabs.html.eex:27 +#: lib/block_scout_web/templates/address/_tabs.html.eex:32 #: lib/block_scout_web/templates/address/overview.html.eex:95 #: lib/block_scout_web/templates/address_validation/index.html.eex:13 #: lib/block_scout_web/views/address_view.ex:302 @@ -215,7 +215,7 @@ msgid "Close" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/_tabs.html.eex:37 +#: lib/block_scout_web/templates/address/_tabs.html.eex:42 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:165 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187 #: lib/block_scout_web/views/address_view.ex:298 @@ -334,6 +334,7 @@ msgid "Curl" msgstr "" #, elixir-format +#: lib/block_scout_web/templates/address_logs/index.html.eex:63 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:18 #: lib/block_scout_web/templates/transaction_log/index.html.eex:67 #: lib/block_scout_web/templates/transaction_log/index.html.eex:133 @@ -505,8 +506,11 @@ msgid "Limit" msgstr "" #, elixir-format +#: lib/block_scout_web/templates/address/_tabs.html.eex:26 +#: lib/block_scout_web/templates/address_logs/index.html.eex:8 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:17 #: lib/block_scout_web/templates/transaction_log/index.html.eex:8 +#: lib/block_scout_web/views/address_view.ex:303 #: lib/block_scout_web/views/transaction_view.ex:340 msgid "Logs" msgstr "" @@ -682,7 +686,7 @@ msgid "Query" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/_tabs.html.eex:53 +#: lib/block_scout_web/templates/address/_tabs.html.eex:58 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:25 #: lib/block_scout_web/views/address_view.ex:300 #: lib/block_scout_web/views/tokens/overview_view.ex:37 @@ -874,6 +878,7 @@ msgid "Top Accounts - %{subnetwork} Explorer" msgstr "" #, elixir-format +#: lib/block_scout_web/templates/address_logs/index.html.eex:33 #: lib/block_scout_web/templates/transaction_log/index.html.eex:103 msgid "Topics" msgstr "" @@ -894,6 +899,7 @@ msgid "Total transactions" msgstr "" #, elixir-format +#: lib/block_scout_web/templates/address_logs/index.html.eex:22 #: lib/block_scout_web/views/transaction_view.ex:287 msgid "Transaction" msgstr "" @@ -1357,7 +1363,7 @@ msgid "Loading balances" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:23 +#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:22 #: lib/block_scout_web/templates/chain/show.html.eex:13 msgid "Loading chart" msgstr "" @@ -1368,7 +1374,7 @@ msgid "There is no coin history for this address." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:26 +#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:25 #: lib/block_scout_web/templates/chain/show.html.eex:16 msgid "There was a problem loading the chart." msgstr "" @@ -1661,7 +1667,7 @@ msgid "Decompiled Code" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/_tabs.html.eex:47 +#: lib/block_scout_web/templates/address/_tabs.html.eex:52 msgid "Decompiled code" msgstr "" @@ -1745,3 +1751,8 @@ msgstr "" #: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:37 msgid "of" msgstr "" + +#, elixir-format, fuzzy +#: lib/block_scout_web/templates/address_logs/index.html.eex:77 +msgid "There are no logs for this address." +msgstr "" From 02c82cea5450a0d6e42b83ae6c5e1c852baea843 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 16 May 2019 15:46:35 +0300 Subject: [PATCH 56/68] make query simpler --- apps/block_scout_web/lib/block_scout_web/chain.ex | 13 ++++++------- apps/explorer/lib/explorer/chain.ex | 7 +++---- apps/explorer/test/explorer/chain_test.exs | 15 +++++---------- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/chain.ex b/apps/block_scout_web/lib/block_scout_web/chain.ex index 6412df44b2..9e82c30023 100644 --- a/apps/block_scout_web/lib/block_scout_web/chain.ex +++ b/apps/block_scout_web/lib/block_scout_web/chain.ex @@ -205,13 +205,12 @@ defmodule BlockScoutWeb.Chain do %{"block_number" => block_number, "transaction_index" => transaction_index, "index" => index} end - defp paging_params(%Log{index: index}) do - %{"index" => index} - end - - defp paging_params({block_number, transaction_index, %Log{index: index}}) - when is_integer(block_number) and is_integer(transaction_index) do - %{"block_number" => block_number, "transaction_index" => transaction_index, "index" => index} + defp paging_params(%Log{index: index} = log) do + if Ecto.assoc_loaded?(log.transaction) do + %{"block_number" => log.transaction.block_number, "transaction_index" => log.transaction.index, "index" => index} + else + %{"index" => index} + end end defp paging_params(%Transaction{block_number: nil, inserted_at: inserted_at, hash: hash}) do diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 4688206cc1..9a3262284f 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -294,17 +294,16 @@ defmodule Explorer.Chain do query = from(log in Log, inner_join: transaction in assoc(log, :transaction), - order_by: [desc: transaction.block_number, desc: transaction.index, asc: log.index], + order_by: [desc: transaction.block_number, desc: transaction.index], preload: [:transaction], where: - (transaction.from_address_hash == ^address_hash or transaction.to_address_hash == ^address_hash or - transaction.created_contract_address_hash == ^address_hash) and log.address_hash == ^address_hash and + log.address_hash == ^address_hash and (transaction.block_number < ^block_number or (transaction.block_number == ^block_number and transaction.index > ^transaction_index) or (transaction.block_number == ^block_number and transaction.index == ^transaction_index and log.index > ^log_index)), limit: ^paging_options.page_size, - select: {transaction.block_number, transaction.index, log} + select: log ) query diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index baf641403b..29f1b68c64 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -81,22 +81,17 @@ defmodule Explorer.ChainTest do log1 = insert(:log, transaction: transaction, index: 1, address: address) - second_page_indexes = - 2..51 - |> Enum.map(fn index -> insert(:log, transaction: transaction, index: index, address: address) end) - |> Enum.map(& &1.index) + 2..51 + |> Enum.map(fn index -> insert(:log, transaction: transaction, index: index, address: address) end) + |> Enum.map(& &1.index) paging_options1 = %PagingOptions{page_size: 1} - [{_, _, log}] = Chain.address_to_logs(address, paging_options: paging_options1) - assert log.index == log1.index + [_log] = Chain.address_to_logs(address, paging_options: paging_options1) paging_options2 = %PagingOptions{page_size: 60, key: {transaction.block_number, transaction.index, log1.index}} - returned_log_indexes = - Chain.address_to_logs(address, paging_options: paging_options2) |> Enum.map(fn {_, _, log} -> log.index end) - - assert second_page_indexes == returned_log_indexes + assert Enum.count(Chain.address_to_logs(address, paging_options: paging_options2)) == 50 end end From 2467d6622239eb88db3be8e83711b38489128285 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 16 May 2019 15:47:22 +0300 Subject: [PATCH 57/68] add forgotten files --- .../controllers/address_logs_controller.ex | 46 +++++++++++ .../templates/address_logs/index.html.eex | 82 +++++++++++++++++++ .../views/address_logs_view.ex | 3 + 3 files changed, 131 insertions(+) create mode 100644 apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex create mode 100644 apps/block_scout_web/lib/block_scout_web/templates/address_logs/index.html.eex create mode 100644 apps/block_scout_web/lib/block_scout_web/views/address_logs_view.ex diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex new file mode 100644 index 0000000000..f79d9aa08d --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex @@ -0,0 +1,46 @@ +defmodule BlockScoutWeb.AddressLogsController do + @moduledoc """ + Manages events logs tab. + """ + + import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1] + import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] + + alias Explorer.{Chain, Market} + alias Explorer.ExchangeRates.Token + alias Indexer.Fetcher.CoinBalanceOnDemand + + use BlockScoutWeb, :controller + + def index(conn, %{"address_id" => address_hash_string} = params) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, address} <- Chain.hash_to_address(address_hash) do + logs_plus_one = Chain.address_to_logs(address, paging_options(params)) + {results, next_page} = split_list_by_page(logs_plus_one) + + next_page_url = + case next_page_params(next_page, results, params) do + nil -> + nil + + next_page_params -> + address_logs_path(conn, :index, address, next_page_params) + end + + render( + conn, + "index.html", + address: address, + logs: results, + coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), + exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + transaction_count: transaction_count(address), + validation_count: validation_count(address), + next_page_url: next_page_url + ) + else + _ -> + not_found(conn) + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_logs/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_logs/index.html.eex new file mode 100644 index 0000000000..22f446924f --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_logs/index.html.eex @@ -0,0 +1,82 @@ +
+ <%= render BlockScoutWeb.AddressView, "overview.html", assigns %> +
+ <%= render BlockScoutWeb.AddressView, "_tabs.html", assigns %> + +
+ +

<%= gettext "Logs" %>

+ + <%= if @next_page_url do %> + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", cur_page_number: "1", show_pagination_limit: true, next_page_path: @next_page_url %> + <% end %> + + <%= if !@next_page_url do %> + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", cur_page_number: "1", show_pagination_limit: true %> + <% end %> + + <%= if Enum.count(@logs) > 0 do %> + <%= for log <- @logs do %> +
+
+
<%= gettext "Transaction" %>
+
+

+ <%= link( + log.transaction, + to: transaction_path(@conn, :show, log.transaction), + "data-test": "log_address_link", + "data-address-hash": log.transaction + ) %> +

+
+
<%= gettext "Topics" %>
+
+
+ <%= unless is_nil(log.first_topic) do %> +
+ [0] + <%= log.first_topic %> +
+ <% end %> + <%= unless is_nil(log.second_topic) do %> +
+ [1] + <%= log.second_topic %> +
+ <% end %> + <%= unless is_nil(log.third_topic) do %> +
+ [2] + <%= log.third_topic %> +
+ <% end %> + <%= unless is_nil(log.fourth_topic) do %> +
+ [3] + <%= log.fourth_topic %> +
+ <% end %> +
+
+
+ <%= gettext "Data" %> +
+
+ <%= unless is_nil(log.data) do %> +
+ <%= log.data %> +
+ <% end %> +
+
+
+ <% end %> + <% else %> +
+ <%= gettext "There are no logs for this address." %> +
+ <% end %> +
+
+
diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_logs_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_logs_view.ex new file mode 100644 index 0000000000..7155e65206 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/address_logs_view.ex @@ -0,0 +1,3 @@ +defmodule BlockScoutWeb.AddressLogsView do + use BlockScoutWeb, :view +end From 4858511b33e44aa43e326f400bcae0d3e9d0c671 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Thu, 16 May 2019 15:50:39 +0300 Subject: [PATCH 58/68] add CHANGELOG entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0eed85aec8..2fb774f4b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - [#1920](https://github.com/poanetwork/blockscout/pull/1920) - fix: remove source code fields from list endpoint - [#1876](https://github.com/poanetwork/blockscout/pull/1876) - async calculate a count of blocks - [#1941](https://github.com/poanetwork/blockscout/pull/1941) - feat: add on demand fetching and stale attr to rpc +- [#1956](https://github.com/poanetwork/blockscout/pull/1956) - add logs tab to address ### Fixes From 6c85f8b7e4b1949b9d1072ebbc957bc785dc6eed Mon Sep 17 00:00:00 2001 From: zachdaniel Date: Wed, 15 May 2019 14:28:05 -0400 Subject: [PATCH 59/68] feat: display init for selfdestructed contracts. --- CHANGELOG.md | 1 + .../templates/address_contract/index.html.eex | 58 +++++++++++++------ .../views/address_contract_view.ex | 13 +++++ apps/block_scout_web/priv/gettext/default.pot | 53 ++++++++++++----- .../priv/gettext/en/LC_MESSAGES/default.po | 57 ++++++++++++------ .../chain/internal_transaction/result.ex | 2 +- .../chain/internal_transaction_test.exs | 4 +- 7 files changed, 134 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ed6b6014d..6131176923 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - [#1876](https://github.com/poanetwork/blockscout/pull/1876) - async calculate a count of blocks - [#1941](https://github.com/poanetwork/blockscout/pull/1941) - feat: add on demand fetching and stale attr to rpc - [#1952](https://github.com/poanetwork/blockscout/pull/1952) - feat: exclude empty contracts by default +- [#1954](https://github.com/poanetwork/blockscout/pull/1954) - feat: use creation init on self destruct ### Fixes diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex index 560440fb4e..b7b2282682 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex @@ -1,18 +1,25 @@ +<% contract_creation_code = contract_creation_code(@address) %> +
<%= render BlockScoutWeb.AddressView, "overview.html", assigns %>
<%= render BlockScoutWeb.AddressView, "_tabs.html", assigns %>
- <%= if !BlockScoutWeb.AddressView.smart_contract_verified?(@address) do %> - <%= link( - gettext("Verify & Publish"), - to: address_verify_contract_path(@conn, :new, @address.hash), - class: "button button-primary button-sm float-right ml-3", - "data-test": "verify_and_publish" - ) %> + <%= if match?({:selfdestructed, _}, contract_creation_code) do %> +
+ <%= gettext("Verify & Publish") %> +
+ <% else %> + <%= if !BlockScoutWeb.AddressView.smart_contract_verified?(@address) do %> + <%= link( + gettext("Verify & Publish"), + to: address_verify_contract_path(@conn, :new, @address.hash), + class: "button button-primary button-sm float-right ml-3", + "data-test": "verify_and_publish" + ) %> + <% end %> <% end %> - <%= if BlockScoutWeb.AddressView.smart_contract_verified?(@address) do %>
@@ -56,15 +63,32 @@ <% end %>
-
-

<%= gettext "Contract creation code" %>

- -
-
-
<%= @address.contract_code %>
-
+ <%= case contract_creation_code do %> + <% {:selfdestructed, transaction_init} -> %> +
+

<%= gettext "Contract Creation Code" %>

+ +
+
+

<%= gettext "Contracts that self destruct in their constructors have no contract code published and cannot be verified." %>

+

<%= gettext "Displaying the init data provided of the creating transaction." %>

+
+
+
<%= transaction_init %>
+
+ <% {:ok, contract_code} -> %> +
+

<%= gettext "Contract Byte Code" %>

+ +
+
+
<%= contract_code %>
+
+ <% end %>
diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex index 27dbd1b559..4dc99a2a68 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex @@ -1,6 +1,8 @@ defmodule BlockScoutWeb.AddressContractView do use BlockScoutWeb, :view + alias Explorer.Chain.{Address, Data, InternalTransaction} + def render("scripts.html", %{conn: conn}) do render_scripts(conn, "address_contract/code_highlighting.js") end @@ -34,4 +36,15 @@ defmodule BlockScoutWeb.AddressContractView do {value, String.pad_leading(to_string(line), max_digits, " ")} end) end + + def contract_creation_code(%Address{ + contract_code: %Data{bytes: <<>>}, + contracts_creation_internal_transaction: %InternalTransaction{init: init} + }) do + {:selfdestructed, init} + end + + def contract_creation_code(%Address{contract_code: contract_code}) do + {:ok, contract_code} + end end diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index 7922c7d2f0..1a6f16d7b1 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -228,7 +228,7 @@ msgid "Compiler" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:23 +#: lib/block_scout_web/templates/address_contract/index.html.eex:30 msgid "Compiler version" msgstr "" @@ -256,7 +256,7 @@ msgid "Connection Lost, click to load newer validations" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:46 +#: lib/block_scout_web/templates/address_contract/index.html.eex:53 msgid "Contract ABI" msgstr "" @@ -289,17 +289,12 @@ msgid "Contract Name" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:60 -msgid "Contract creation code" -msgstr "" - -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:19 +#: lib/block_scout_web/templates/address_contract/index.html.eex:26 msgid "Contract name:" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:34 +#: lib/block_scout_web/templates/address_contract/index.html.eex:41 msgid "Contract source code" msgstr "" @@ -621,7 +616,7 @@ msgid "Older" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:27 +#: lib/block_scout_web/templates/address_contract/index.html.eex:34 msgid "Optimization enabled" msgstr "" @@ -982,7 +977,8 @@ msgid "Value" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:9 +#: lib/block_scout_web/templates/address_contract/index.html.eex:11 +#: lib/block_scout_web/templates/address_contract/index.html.eex:16 msgid "Verify & Publish" msgstr "" @@ -1047,7 +1043,7 @@ msgid "at" msgstr "" #, elixir-format -#: lib/block_scout_web/views/address_contract_view.ex:20 +#: lib/block_scout_web/views/address_contract_view.ex:22 msgid "false" msgstr "" @@ -1065,7 +1061,7 @@ msgid "string" msgstr "" #, elixir-format -#: lib/block_scout_web/views/address_contract_view.ex:19 +#: lib/block_scout_web/views/address_contract_view.ex:21 msgid "true" msgstr "" @@ -1509,17 +1505,17 @@ msgid "Support" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:48 +#: lib/block_scout_web/templates/address_contract/index.html.eex:55 msgid "Copy ABI" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:62 +#: lib/block_scout_web/templates/address_contract/index.html.eex:71 msgid "Copy Contract Creation Code" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:36 +#: lib/block_scout_web/templates/address_contract/index.html.eex:43 msgid "Copy Source Code" msgstr "" @@ -1745,3 +1741,28 @@ msgstr "" #: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:37 msgid "of" msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:83 +msgid "Contract Byte Code" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:69 +msgid "Contract Creation Code" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:75 +msgid "Contracts that self destruct in their constructors have no contract code published and cannot be verified." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:85 +msgid "Copy Contract Byte Code" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:76 +msgid "Displaying the init data provided of the creating transaction." +msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index 8925a95acf..2f8d4a3d47 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -228,7 +228,7 @@ msgid "Compiler" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:23 +#: lib/block_scout_web/templates/address_contract/index.html.eex:30 msgid "Compiler version" msgstr "" @@ -256,7 +256,7 @@ msgid "Connection Lost, click to load newer validations" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:46 +#: lib/block_scout_web/templates/address_contract/index.html.eex:53 msgid "Contract ABI" msgstr "" @@ -289,17 +289,12 @@ msgid "Contract Name" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:60 -msgid "Contract creation code" -msgstr "" - -#, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:19 +#: lib/block_scout_web/templates/address_contract/index.html.eex:26 msgid "Contract name:" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:34 +#: lib/block_scout_web/templates/address_contract/index.html.eex:41 msgid "Contract source code" msgstr "" @@ -621,7 +616,7 @@ msgid "Older" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:27 +#: lib/block_scout_web/templates/address_contract/index.html.eex:34 msgid "Optimization enabled" msgstr "" @@ -982,7 +977,8 @@ msgid "Value" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:9 +#: lib/block_scout_web/templates/address_contract/index.html.eex:11 +#: lib/block_scout_web/templates/address_contract/index.html.eex:16 msgid "Verify & Publish" msgstr "" @@ -1047,7 +1043,7 @@ msgid "at" msgstr "" #, elixir-format -#: lib/block_scout_web/views/address_contract_view.ex:20 +#: lib/block_scout_web/views/address_contract_view.ex:22 msgid "false" msgstr "" @@ -1065,7 +1061,7 @@ msgid "string" msgstr "" #, elixir-format -#: lib/block_scout_web/views/address_contract_view.ex:19 +#: lib/block_scout_web/views/address_contract_view.ex:21 msgid "true" msgstr "" @@ -1357,7 +1353,7 @@ msgid "Loading balances" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:23 +#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:22 #: lib/block_scout_web/templates/chain/show.html.eex:13 msgid "Loading chart" msgstr "" @@ -1368,7 +1364,7 @@ msgid "There is no coin history for this address." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:26 +#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:25 #: lib/block_scout_web/templates/chain/show.html.eex:16 msgid "There was a problem loading the chart." msgstr "" @@ -1509,17 +1505,17 @@ msgid "Support" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:48 +#: lib/block_scout_web/templates/address_contract/index.html.eex:55 msgid "Copy ABI" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:62 +#: lib/block_scout_web/templates/address_contract/index.html.eex:71 msgid "Copy Contract Creation Code" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_contract/index.html.eex:36 +#: lib/block_scout_web/templates/address_contract/index.html.eex:43 msgid "Copy Source Code" msgstr "" @@ -1745,3 +1741,28 @@ msgstr "" #: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:37 msgid "of" msgstr "" + +#, elixir-format, fuzzy +#: lib/block_scout_web/templates/address_contract/index.html.eex:83 +msgid "Contract Byte Code" +msgstr "" + +#, elixir-format, fuzzy +#: lib/block_scout_web/templates/address_contract/index.html.eex:69 +msgid "Contract Creation Code" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:75 +msgid "Contracts that self destruct in their constructors have no contract code published and cannot be verified." +msgstr "" + +#, elixir-format, fuzzy +#: lib/block_scout_web/templates/address_contract/index.html.eex:85 +msgid "Copy Contract Byte Code" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:76 +msgid "Displaying the init data provided of the creating transaction." +msgstr "" diff --git a/apps/explorer/lib/explorer/chain/internal_transaction/result.ex b/apps/explorer/lib/explorer/chain/internal_transaction/result.ex index 5b4e3102fc..c4c680e7d4 100644 --- a/apps/explorer/lib/explorer/chain/internal_transaction/result.ex +++ b/apps/explorer/lib/explorer/chain/internal_transaction/result.ex @@ -19,7 +19,7 @@ defmodule Explorer.Chain.InternalTransaction.Result do {key, to_string(hash)} end - defp entry_to_raw({"code", _} = entry), do: entry + defp entry_to_raw({"code", code}), do: {"code", Data.to_string(code)} defp entry_to_raw({key, decimal}) when key in ~w(gasUsed) do integer = diff --git a/apps/explorer/test/explorer/chain/internal_transaction_test.exs b/apps/explorer/test/explorer/chain/internal_transaction_test.exs index 54ace519bd..fc3977a998 100644 --- a/apps/explorer/test/explorer/chain/internal_transaction_test.exs +++ b/apps/explorer/test/explorer/chain/internal_transaction_test.exs @@ -1,7 +1,7 @@ defmodule Explorer.Chain.InternalTransactionTest do use Explorer.DataCase - alias Explorer.Chain.{InternalTransaction, Wei} + alias Explorer.Chain.{Data, InternalTransaction, Wei} alias Explorer.Factory import EthereumJSONRPC, only: [integer_to_quantity: 1] @@ -173,7 +173,7 @@ defmodule Explorer.Chain.InternalTransactionTest do end test "it correctly formats a create" do - contract_code = Factory.contract_code_info().bytecode + {:ok, contract_code} = Data.cast(Factory.contract_code_info().bytecode) contract_address = Factory.address_hash() from = Factory.address_hash() gas = 50_000 From 6b2475fa5480b6958825b21bb8c2c4a7a6b44b40 Mon Sep 17 00:00:00 2001 From: Andrew Gross Date: Thu, 16 May 2019 13:09:38 -0600 Subject: [PATCH 60/68] organize README, add links to external resources --- README.md | 179 +++++++++++++++--------------------------------------- 1 file changed, 49 insertions(+), 130 deletions(-) diff --git a/README.md b/README.md index 9755475dbe..47dc73ce51 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,9 @@ BlockScout provides a comprehensive, easy-to-use interface for users to view, co Following is an overview of the project and instructions for [getting started](#getting-started). -Visit the [POA BlockScout forum](https://forum.poa.network/c/blockscout) or the [Gitter Channel](https://gitter.im/poanetwork/blockscout) to access additional information or post questions. +Visit the [POA BlockScout forum](https://forum.poa.network/c/blockscout) for additional deployment instructions, FAQs, troubleshooting, and other BlockScout related items. You can also post and answer questions here. + +You can also access the dev chatroom on our [Gitter Channel](https://gitter.im/poanetwork/blockscout). ## About BlockScout @@ -41,31 +43,16 @@ Currently available block explorers (i.e. Etherscan and Etherchain) are closed s ### Supported Projects -#### Hosted Chains - -* [POA Core Network](https://blockscout.com/poa/core) -* [POA Sokol Testnet](https://blockscout.com/poa/sokol) -* [xDai Chain](https://blockscout.com/poa/dai) -* [Ethereum Mainnet](https://blockscout.com/eth/mainnet) -* [Kovan Testnet](https://blockscout.com/eth/kovan) -* [Ropsten Testnet](https://blockscout.com/eth/ropsten) -* [Goerli Testnet](https://blockscout.com/eth/goerli) -* [Rinkeby Testnet](https://blockscout.com/eth/rinkeby) -* [Ethereum Classic](https://blockscout.com/etc/mainnet) -* [Aerum](https://blockscout.com/aerum/mainnet) -* [Callisto](https://blockscout.com/callisto/mainnet) -* [RSK](https://blockscout.com/rsk/mainnet) - -#### Additional Chains Utilizing BlockScout - -* [Oasis Labs](https://blockexplorer.oasiscloud.io/) -* [Fuse Network](https://explorer.fuse.io/) -* [ARTIS](https://explorer.sigma1.artis.network) -* [SafeChain](https://explorer.safechain.io) -* [SpringChain](https://explorer.springrole.com/) -* [PIRL](http://pirl.es/) -* [Petrichor](https://explorer.petrichor-dev.com/) -* [Ether-1](https://blocks.ether1.wattpool.net/) +| **Hosted Mainnets** | **Hosted Testnets** | **Additional Chains using BlockScout** | +|--------------------------------------------------------|-------------------------------------------------------|----------------------------------------------------| +| [Aerum](https://blockscout.com/aerum/mainnet) | [Goerli Testnet](https://blockscout.com/eth/goerli) | [ARTIS](https://explorer.sigma1.artis.network) | +| [Callisto](https://blockscout.com/callisto/mainnet) | [Kovan Testnet](https://blockscout.com/eth/kovan) | [Ether-1](https://blocks.ether1.wattpool.net/) | +| [Ethereum Classic](https://blockscout.com/etc/mainnet) | [POA Sokol Testnet](https://blockscout.com/poa/sokol) | [Fuse Network](https://explorer.fuse.io/) | +| [Ethereum Mainnet](https://blockscout.com/eth/mainnet) | [Rinkeby Testnet](https://blockscout.com/eth/rinkeby) | [Oasis Labs](https://blockexplorer.oasiscloud.io/) | +| [POA Core Network](https://blockscout.com/poa/core) | [Ropsten Testnet](https://blockscout.com/eth/ropsten) | [Petrichor](https://explorer.petrichor-dev.com/) | +| [RSK](https://blockscout.com/rsk/mainnet) | | [PIRL](http://pirl.es/) | +| [xDai Chain](https://blockscout.com/poa/dai) | | [SafeChain](https://explorer.safechain.io) | +| | | [SpringChain](https://explorer.springrole.com/) | ### Visual Interface @@ -74,13 +61,24 @@ Interface for the POA network _updated 02/2019_ ![BlockScout Example](explorer_example_2_2019.gif) -## Getting Started -We use [Terraform](https://www.terraform.io/intro/getting-started/install.html) to build the correct infrastructure to run BlockScout. See [https://github.com/poanetwork/blockscout-terraform](https://github.com/poanetwork/blockscout-terraform) for details. +### Umbrella Project Organization -### Requirements +This repository is an [umbrella project](https://elixir-lang.org/getting-started/mix-otp/dependencies-and-umbrella-projects.html). Each directory under `apps/` is a separate [Mix](https://hexdocs.pm/mix/Mix.html) project and [OTP application](https://hexdocs.pm/elixir/Application.html), but the projects can use each other as a dependency in their `mix.exs`. -The [development stack page](https://github.com/poanetwork/blockscout/wiki/Development-Stack) contains more information about these frameworks. +Each OTP application has a restricted domain. + +| Directory | OTP Application | Namespace | Purpose | +|:------------------------|:--------------------|:------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `apps/ethereum_jsonrpc` | `:ethereum_jsonrpc` | `EthereumJSONRPC` | Ethereum JSONRPC client. It is allowed to know `Explorer`'s param format, but it cannot directly depend on `:explorer` | +| `apps/explorer` | `:explorer` | `Explorer` | Storage for the indexed chain. Can read and write to the backing storage. MUST be able to boot in a read-only mode when run independently from `:indexer`, so cannot depend on `:indexer` as that would start `:indexer` indexing. | +| `apps/block_scout_web` | `:block_scout_web` | `BlockScoutWeb` | Phoenix interface to `:explorer`. The minimum interface to allow web access should go in `:block_scout_web`. Any business rules or interface not tied directly to `Phoenix` or `Plug` should go in `:explorer`. MUST be able to boot in a read-only mode when run independently from `:indexer`, so cannot depend on `:indexer` as that would start `:indexer` indexing. | +| `apps/indexer` | `:indexer` | `Indexer` | Uses `:ethereum_jsonrpc` to index chain and batch import data into `:explorer`. Any process, `Task`, or `GenServer` that automatically reads from the chain and writes to `:explorer` should be in `:indexer`. This restricts automatic writes to `:indexer` and read-only mode can be achieved by not running `:indexer`. | + + +## Getting Started + +### Requirements | Dependency | Mac | Linux | |-------------|-----|-------| @@ -96,121 +94,42 @@ The [development stack page](https://github.com/poanetwork/blockscout/wiki/Devel ### Build and Run - 1. Clone the repository. - `git clone https://github.com/poanetwork/blockscout` - - 2. Go to the explorer subdirectory. - `cd blockscout` - - 3. Set up default configurations. - `cp apps/explorer/config/dev.secret.exs.example apps/explorer/config/dev.secret.exs` - `cp apps/block_scout_web/config/dev.secret.exs.example apps/block_scout_web/config/dev.secret.exs` -
Linux: Update the database username and password configuration in `apps/explorer/config/dev.secret.exs` -
Mac: Remove the `username` and `password` fields from `apps/explorer/config/dev.secret.exs` -
Optional: Set up default configuration for testing. - `cp apps/explorer/config/test.secret.exs.example apps/explorer/config/test.secret.exs` - Example usage: Changing the default Postgres port from localhost:15432 if [Boxen](https://github.com/boxen/boxen) is installed. - - 4. Install dependencies. - `mix do deps.get, local.rebar --force, deps.compile, compile` - - 5. Create and migrate database. - `mix ecto.create && mix ecto.migrate` -
_Note:_ If you have run previously, drop the previous database - `mix do ecto.drop, ecto.create, ecto.migrate` - - 6. Install Node.js dependencies. - `cd apps/block_scout_web/assets && npm install; cd -` - `cd apps/explorer && npm install; cd -` +#### Playbook Deployment - 7. Update your JSON RPC Variant in `apps/explorer/config/dev.exs` and `apps/indexer/config/dev.exs`. - For `variant`, enter `ganache`, `geth`, `parity`, or `rsk` +We use [Ansible](https://docs.ansible.com/ansible/latest/index.html) & [Terraform](https://www.terraform.io/intro/getting-started/install.html) to build the correct infrastructure to run BlockScout. See [https://github.com/poanetwork/blockscout-terraform](https://github.com/poanetwork/blockscout-terraform) for details and instructions. - 8. Update your JSON RPC Endpoint in `apps/explorer/config/dev/` and `apps/indexer/config/dev/` - For the `variant` chosen in step 7, enter the correct information for the corresponding JSON RPC Endpoint in `parity.exs`, `geth.exs`, or `ganache.exs` +#### Manual Deployment - 9. Enable HTTPS in development. The Phoenix server only runs with HTTPS. - * `cd apps/block_scout_web` - * `mix phx.gen.cert blockscout blockscout.local; cd -` - * Add blockscout and blockscout.local to your `/etc/hosts` - ``` - 127.0.0.1 localhost blockscout blockscout.local - 255.255.255.255 broadcasthost - ::1 localhost blockscout blockscout.local - ``` - * If using Chrome, Enable `chrome://flags/#allow-insecure-localhost`. +See [Manual BlockScout Deployment](https://forum.poa.network/t/manual-blockscout-deployment/2458) for instructions. - 9. Run the Phoenix Server from the root directory of your application. - `mix phx.server` +#### Environment Variables -Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. +Our forum contains a [full list of BlockScout environment variables](https://forum.poa.network/t/faq-blockscout-environment-variables/1814). -_Additional runtime options:_ +#### Configuring EVM Chains -* Run Phoenix Server with IEx (Interactive Elixer) -`iex -S mix phx.server` +* **CSS:** Update the import instruction in `apps/block_scout_web/assets/css/theme/_variables.scss` to select a preset css file. This is reflected in the `production-${chain}` branch for each instance. For example, in the `production-xdai` branch, it is set to `@import "dai-variables"`. -* Run Phoenix Server with real time indexer -`iex -S mix phx.server` +* **ENV:** Update the [environment variables](https://forum.poa.network/t/faq-blockscout-environment-variables/1814) to match the chain specs. -### Automating Restarts +#### Automating Restarts By default `blockscout` does not restart if it crashes. To enable automated -restarts, set the environment variable `HEART_COMMAND` to whatever you run to -start `blockscout`. You can configure the heart beat timeout, which will change -how long it will wait before considering the application to be unresponsive. At -that point, it will kill the current blockscout and execute `HEART_COMMAND`. -By default a crash dump is not written unless you set `ERL_CRASH_DUMP_SECONDS` -to a positive or negative integer. See the documentation for -[heart](http://erlang.org/doc/man/heart.html) for more information. +restarts, set the environment variable `HEART_COMMAND` to whatever command you run to start `blockscout`. Configure the heart beat timeout to change how long it waits before considering the application unresponsive. At that point, it will kill the current blockscout instance and execute the `HEART_COMMAND`. By default a crash dump is not written unless you set `ERL_CRASH_DUMP_SECONDS` to a positive or negative integer. See the [heart](http://erlang.org/doc/man/heart.html) documentation for more information. -### Configuring Ethereum Classic and other EVM Chains -**Note: Most of these modifications will be consolidated into a single file in the future.** - - 1. Update the import file in `apps/block_scout_web/assets/css/theme/_variables.scss`. There are several preset css files for our supported chains which include Ethereum Classic, Ethereum Mainnet, Ropsten Testnet, Kovan Testnet, POA Core, and POA Sokol. To deploy Ethereum Classic, change the import to `ethereum_classic_variables`. - - 2. Update the logo file in `apps/block_scout_web/config/config.exs`. To deploy Ethereum Classic, change this file to `classic_ethereum_logo.svg`. - - 3. Update the `check_origin` configuration in `apps/block_scout_web/config/prod.exs`. This allows realtime events to occur on your endpoint. - - 4. Update the node configuration. You will need a full tracing node with WebSockets enabled. Make the changes in the following files (dev/prod): - - * `apps/explorer/config/dev/parity.exs` - * `apps/explorer/config/prod/parity.exs` - * `apps/indexer/config/dev/parity.exs` - * `apps/indexer/config/prod/parity.exs` - - 5. Update the dropdown menu in the main navigation `apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex` - - 6. Update the coin in `apps/explorer/config/config.exs`. This will pull relevant information from Coinmarketcap.com. - -### Umbrella Project Organization - -This repository is an [umbrella project](https://elixir-lang.org/getting-started/mix-otp/dependencies-and-umbrella-projects.html). Each directory under `apps/` is a separate [Mix](https://hexdocs.pm/mix/Mix.html) project and [OTP application](https://hexdocs.pm/elixir/Application.html), but the projects can use each other as a dependency in their `mix.exs`. - -Each OTP application has a restricted domain. - -| Directory | OTP Application | Namespace | Purpose | -|:------------------------|:--------------------|:------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `apps/ethereum_jsonrpc` | `:ethereum_jsonrpc` | `EthereumJSONRPC` | Ethereum JSONRPC client. It is allowed to know `Explorer`'s param format, but it cannot directly depend on `:explorer` | -| `apps/explorer` | `:explorer` | `Explorer` | Storage for the indexed chain. Can read and write to the backing storage. MUST be able to boot in a read-only mode when run independently from `:indexer`, so cannot depend on `:indexer` as that would start `:indexer` indexing. | -| `apps/block_scout_web` | `:block_scout_web` | `BlockScoutWeb` | Phoenix interface to `:explorer`. The minimum interface to allow web access should go in `:block_scout_web`. Any business rules or interface not tied directly to `Phoenix` or `Plug` should go in `:explorer`. MUST be able to boot in a read-only mode when run independently from `:indexer`, so cannot depend on `:indexer` as that would start `:indexer` indexing. | -| `apps/indexer` | `:indexer` | `Indexer` | Uses `:ethereum_jsonrpc` to index chain and batch import data into `:explorer`. Any process, `Task`, or `GenServer` that automatically reads from the chain and writes to `:explorer` should be in `:indexer`. This restricts automatic writes to `:indexer` and read-only mode can be achieved by not running `:indexer`. | - - -### CircleCI Updates +#### CircleCI Updates To monitor build status, configure your local [CCMenu](http://ccmenu.org/) with the following url: [`https://circleci.com/gh/poanetwork/blockscout.cc.xml?circle-token=f8823a3d0090407c11f87028c73015a331dbf604`](https://circleci.com/gh/poanetwork/blockscout.cc.xml?circle-token=f8823a3d0090407c11f87028c73015a331dbf604) -### Testing +## Testing -#### Requirements +### Requirements * PhantomJS (for wallaby) -#### Running the tests +### Running the tests 1. Build the assets. `cd apps/block_scout_web/assets && npm run build; cd -` @@ -237,9 +156,9 @@ To monitor build status, configure your local [CCMenu](http://ccmenu.org/) with 8. Test the JavaScript code. `cd apps/block_scout_web/assets && npm run test; cd -` -##### Parity +#### Parity -###### Mox +##### Mox **This is the default setup. `mix coveralls.html --umbrella` will work on its own, but to be explicit, use the following setup**: @@ -249,7 +168,7 @@ export ETHEREUM_JSONRPC_WEB_SOCKET_CASE=EthereumJSONRPC.WebSocket.Case.Mox mix coveralls.html --umbrella --exclude no_parity ``` -###### HTTP / WebSocket +##### HTTP / WebSocket ```shell export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Parity.HTTPWebSocket @@ -262,9 +181,9 @@ mix coveralls.html --umbrella --exclude no_parity | HTTP | `http://localhost:8545` | | WebSocket | `ws://localhost:8546` | -##### Geth +#### Geth -###### Mox +##### Mox ```shell export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Geth.Mox @@ -272,7 +191,7 @@ export ETHEREUM_JSONRPC_WEB_SOCKET_CASE=EthereumJSONRPC.WebSocket.Case.Mox mix coveralls.html --umbrella --exclude no_geth ``` -###### HTTP / WebSocket +##### HTTP / WebSocket ```shell export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Geth.HTTPWebSocket From a24314ff72f77a90152966b41a843fd227fdf299 Mon Sep 17 00:00:00 2001 From: saneery Date: Fri, 17 May 2019 15:12:25 +0300 Subject: [PATCH 61/68] remove epoch counter table checking in init function --- .../explorer/lib/explorer/staking/epoch_counter.ex | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/explorer/lib/explorer/staking/epoch_counter.ex b/apps/explorer/lib/explorer/staking/epoch_counter.ex index a53425ce9c..586b209e42 100644 --- a/apps/explorer/lib/explorer/staking/epoch_counter.ex +++ b/apps/explorer/lib/explorer/staking/epoch_counter.ex @@ -44,14 +44,12 @@ defmodule Explorer.Staking.EpochCounter do end def init([]) do - if :ets.whereis(@table_name) == :undefined do - :ets.new(@table_name, [ - :set, - :named_table, - :public, - write_concurrency: true - ]) - end + :ets.new(@table_name, [ + :set, + :named_table, + :public, + write_concurrency: true + ]) Subscriber.to(:blocks, :realtime) {:ok, [], {:continue, :epoch_info}} From c1f49ceda68248f47f034f1840aba924ded471ce Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Fri, 17 May 2019 16:33:31 +0300 Subject: [PATCH 62/68] add log index to transaction view --- .../lib/block_scout_web/views/api/rpc/transaction_view.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex index 459272df07..e792be8f40 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex @@ -77,7 +77,8 @@ defmodule BlockScoutWeb.API.RPC.TransactionView do %{ "address" => "#{log.address_hash}", "topics" => get_topics(log), - "data" => "#{log.data}" + "data" => "#{log.data}", + "index" => "#{log.index}" } end From 0ed3ce883884166a6481d9ece70ff5d382208451 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Fri, 17 May 2019 16:52:13 +0300 Subject: [PATCH 63/68] fix tests --- .../controllers/api/rpc/transaction_controller_test.exs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs index 92a2f969ab..9d151e382c 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs @@ -460,7 +460,8 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do %{ "address" => "#{address.hash}", "data" => "#{log.data}", - "topics" => ["first topic", "second topic", nil, nil] + "topics" => ["first topic", "second topic", nil, nil], + "index" => "#{log.index}" } ], "next_page_params" => nil From 7b881fff0deca0150488fc3d900842f5b57820dc Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Fri, 17 May 2019 20:11:07 +0300 Subject: [PATCH 64/68] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9755475dbe..b727aba8c0 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Currently available block explorers (i.e. Etherscan and Etherchain) are closed s * [SafeChain](https://explorer.safechain.io) * [SpringChain](https://explorer.springrole.com/) * [PIRL](http://pirl.es/) -* [Petrichor](https://explorer.petrichor-dev.com/) +* [Petrichor](https://explorer.petrachor.com/) * [Ether-1](https://blocks.ether1.wattpool.net/) From a3ea06cc4a6a208cae902bfea4e0433e96367b6a Mon Sep 17 00:00:00 2001 From: saneery Date: Sun, 19 May 2019 17:09:21 +0300 Subject: [PATCH 65/68] fix error with access behavior --- apps/explorer/lib/explorer/staking/epoch_counter.ex | 2 +- apps/explorer/test/explorer/staking/epoch_counter_test.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/explorer/lib/explorer/staking/epoch_counter.ex b/apps/explorer/lib/explorer/staking/epoch_counter.ex index 586b209e42..c35ec1a6c9 100644 --- a/apps/explorer/lib/explorer/staking/epoch_counter.ex +++ b/apps/explorer/lib/explorer/staking/epoch_counter.ex @@ -64,7 +64,7 @@ defmodule Explorer.Staking.EpochCounter do def handle_info({:chain_event, :blocks, :realtime, blocks}, state) do new_block_number = blocks - |> Enum.map(& &1[:number]) + |> Enum.map(&Map.get(&1, :number, 0)) |> Enum.max(fn -> 0 end) case :ets.lookup(@table_name, @epoch_end_key) do diff --git a/apps/explorer/test/explorer/staking/epoch_counter_test.exs b/apps/explorer/test/explorer/staking/epoch_counter_test.exs index 5232a71edb..278377ce05 100644 --- a/apps/explorer/test/explorer/staking/epoch_counter_test.exs +++ b/apps/explorer/test/explorer/staking/epoch_counter_test.exs @@ -37,7 +37,7 @@ defmodule Explorer.Staking.EpochCounterTest do event_type = :blocks broadcast_type = :realtime - event_data = [%{number: 881}] + event_data = [%Explorer.Chain.Block{number: 881}] set_mox(11, 960) Publisher.broadcast([{event_type, event_data}], broadcast_type) From 8b2b84617518740c3435fdec3d178ddb403fdc16 Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 20 May 2019 16:53:48 +0300 Subject: [PATCH 66/68] add CHANGELOG entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8c086b1b8..1e60b218c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,7 @@ - [#1900](https://github.com/poanetwork/blockscout/pull/1900) - SUPPORTED_CHAINS ENV var - [#1892](https://github.com/poanetwork/blockscout/pull/1892) - Remove temporary worker modules - [#1958](https://github.com/poanetwork/blockscout/pull/1958) - Default value for release link env var +- [#1975](https://github.com/poanetwork/blockscout/pull/1975) - add log index to transaction view ## 1.3.10-beta From e10e54c4a0b799143daf8a3ca28cdebfe42919ab Mon Sep 17 00:00:00 2001 From: Ayrat Badykov Date: Mon, 20 May 2019 17:29:11 +0300 Subject: [PATCH 67/68] add log index to documentation --- apps/block_scout_web/lib/block_scout_web/etherscan.ex | 5 +++++ 1 file changed, 5 insertions(+) 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 5f113a65df..3167689f3c 100644 --- a/apps/block_scout_web/lib/block_scout_web/etherscan.ex +++ b/apps/block_scout_web/lib/block_scout_web/etherscan.ex @@ -594,6 +594,11 @@ defmodule BlockScoutWeb.Etherscan do type: "block number", definition: "A nonnegative number used to identify blocks.", example: ~s("0x5c958") + }, + index: %{ + type: "log index", + definition: "A nonnegative number used to identify logs.", + example: ~s("1") } } } From a68c822b8408e3e06022cab9208531b145351724 Mon Sep 17 00:00:00 2001 From: saneery Date: Mon, 20 May 2019 18:30:42 +0300 Subject: [PATCH 68/68] staking pools factory --- .../import/runner/staking_pools_test.exs | 82 ++----------------- apps/explorer/test/support/factory.ex | 20 +++++ 2 files changed, 27 insertions(+), 75 deletions(-) diff --git a/apps/explorer/test/explorer/chain/import/runner/staking_pools_test.exs b/apps/explorer/test/explorer/chain/import/runner/staking_pools_test.exs index 5f5ec5a3be..af25368679 100644 --- a/apps/explorer/test/explorer/chain/import/runner/staking_pools_test.exs +++ b/apps/explorer/test/explorer/chain/import/runner/staking_pools_test.exs @@ -1,83 +1,14 @@ defmodule Explorer.Chain.Import.Runner.StakingPoolsTest do use Explorer.DataCase + import Explorer.Factory + alias Ecto.Multi alias Explorer.Chain.Import.Runner.StakingPools describe "run/1" do test "insert new pools list" do - pools = - [pool1, pool2, pool3] = [ - %{ - address_hash: %Explorer.Chain.Hash{ - byte_count: 20, - bytes: <<11, 47, 94, 47, 60, 189, 134, 78, 170, 44, 100, 46, 55, 105, 193, 88, 35, 97, 202, 246>> - }, - metadata: %{ - banned_unitil: 0, - delegators_count: 0, - is_active: true, - is_banned: false, - is_validator: true, - mining_address: %Explorer.Chain.Hash{ - byte_count: 20, - bytes: <<187, 202, 168, 212, 130, 137, 187, 31, 252, 249, 128, 141, 154, 164, 177, 210, 21, 5, 76, 120>> - }, - retries_count: 1, - staked_amount: 25, - was_banned_count: 0, - was_validator_count: 1 - }, - name: "anonymous", - primary: true - }, - %{ - address_hash: %Explorer.Chain.Hash{ - byte_count: 20, - bytes: <<170, 148, 182, 135, 211, 249, 85, 42, 69, 59, 129, 178, 131, 76, 165, 55, 120, 152, 13, 192>> - }, - metadata: %{ - banned_unitil: 0, - delegators_count: 0, - is_active: true, - is_banned: false, - is_validator: true, - mining_address: %Explorer.Chain.Hash{ - byte_count: 20, - bytes: <<117, 223, 66, 56, 58, 254, 107, 245, 25, 74, 168, 250, 14, 155, 61, 95, 158, 134, 148, 65>> - }, - retries_count: 1, - staked_amount: 25, - was_banned_count: 0, - was_validator_count: 1 - }, - name: "anonymous", - primary: true - }, - %{ - address_hash: %Explorer.Chain.Hash{ - byte_count: 20, - bytes: <<49, 44, 35, 14, 125, 109, 176, 82, 36, 246, 2, 8, 166, 86, 227, 84, 28, 92, 66, 186>> - }, - metadata: %{ - banned_unitil: 0, - delegators_count: 0, - is_active: true, - is_banned: false, - is_validator: true, - mining_address: %Explorer.Chain.Hash{ - byte_count: 20, - bytes: <<82, 45, 243, 150, 174, 112, 160, 88, 189, 105, 119, 132, 8, 99, 15, 219, 2, 51, 137, 178>> - }, - retries_count: 1, - staked_amount: 0, - was_banned_count: 0, - was_validator_count: 1 - }, - name: "anonymous", - primary: true - } - ] + pools = [pool1, pool2, pool3, pool4] = build_list(4, :staking_pool) assert {:ok, %{insert_staking_pools: list}} = run_changes(pools) assert Enum.count(list) == Enum.count(pools) @@ -89,9 +20,10 @@ defmodule Explorer.Chain.Import.Runner.StakingPoolsTest do Map.put(acc, pool.address_hash, pool) end) - assert saved_list[pool1.address_hash].metadata["staked_ratio"] == 0.5 - assert saved_list[pool2.address_hash].metadata["staked_ratio"] == 0.5 - assert saved_list[pool3.address_hash].metadata["staked_ratio"] == 0.0 + assert saved_list[pool1.address_hash].metadata["staked_ratio"] == 0.25 + assert saved_list[pool2.address_hash].metadata["staked_ratio"] == 0.25 + assert saved_list[pool3.address_hash].metadata["staked_ratio"] == 0.25 + assert saved_list[pool4.address_hash].metadata["staked_ratio"] == 0.25 end end diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index 4c030a4db1..115b07e066 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -609,4 +609,24 @@ defmodule Explorer.Factory do user: build(:user) } end + + def staking_pool_factory do + %{ + address_hash: address_hash(), + metadata: %{ + banned_unitil: 0, + delegators_count: 0, + is_active: true, + is_banned: false, + is_validator: true, + mining_address: address_hash(), + retries_count: 1, + staked_amount: 25, + was_banned_count: 0, + was_validator_count: 1 + }, + name: "anonymous", + primary: true + } + end end