From 3c4d63149c4816a6ddc1b285a2a04f7670340aef Mon Sep 17 00:00:00 2001 From: saneery Date: Tue, 7 May 2019 13:06:44 +0300 Subject: [PATCH 01/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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 8645e02e3c9ce62f55dde418679f355fde8f4934 Mon Sep 17 00:00:00 2001 From: saneery Date: Tue, 14 May 2019 13:47:19 +0300 Subject: [PATCH 16/23] 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 6b2475fa5480b6958825b21bb8c2c4a7a6b44b40 Mon Sep 17 00:00:00 2001 From: Andrew Gross Date: Thu, 16 May 2019 13:09:38 -0600 Subject: [PATCH 17/23] 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 18/23] 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 19/23] 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 20/23] 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 a3ea06cc4a6a208cae902bfea4e0433e96367b6a Mon Sep 17 00:00:00 2001 From: saneery Date: Sun, 19 May 2019 17:09:21 +0300 Subject: [PATCH 21/23] 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 22/23] 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 23/23] 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") } } }