Skip to content

Commit

Permalink
Merge pull request #1801 from poanetwork/pools_fetching
Browse files Browse the repository at this point in the history
Staking pools fetching
  • Loading branch information
vbaranov committed May 2, 2019
2 parents e998306 + 5ee791c commit 2d64421
Show file tree
Hide file tree
Showing 19 changed files with 2,320 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
- [#1777](https://github.com/poanetwork/blockscout/pull/1777) - show ERC-20 token transfer info on transaction page
- [#1770](https://github.com/poanetwork/blockscout/pull/1770) - set a websocket keepalive from config
- [#1789](https://github.com/poanetwork/blockscout/pull/1789) - add ERC-721 info to transaction overview page
- [#1801](https://github.com/poanetwork/blockscout/pull/1801) - Staking pools fetching

### Fixes

Expand Down
4 changes: 4 additions & 0 deletions apps/explorer/config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ else
config :explorer, Explorer.Validator.MetadataProcessor, enabled: false
end

config :explorer, Explorer.Staking.PoolsReader,
validators_contract_address: System.get_env("POS_VALIDATORS_CONTRACT"),
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
Expand Down
88 changes: 88 additions & 0 deletions apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
defmodule Explorer.Chain.Import.Runner.StakingPools do
@moduledoc """
Bulk imports staking pools to Address.Name tabe.
"""

require Ecto.Query

alias Ecto.{Changeset, Multi, Repo}
alias Explorer.Chain.{Address, Import}

import Ecto.Query, only: [from: 2]

@behaviour Import.Runner

# milliseconds
@timeout 60_000

@type imported :: [Address.Name.t()]

@impl Import.Runner
def ecto_schema_module, do: Address.Name

@impl Import.Runner
def option_key, do: :staking_pools

@impl Import.Runner
def imported_table_row do
%{
value_type: "[#{ecto_schema_module()}.t()]",
value_description: "List of `t:#{ecto_schema_module()}.t/0`s"
}
end

@impl Import.Runner
def run(multi, changes_list, %{timestamps: timestamps} = options) do
insert_options =
options
|> Map.get(option_key(), %{})
|> Map.take(~w(on_conflict timeout)a)
|> Map.put_new(:timeout, @timeout)
|> Map.put(:timestamps, timestamps)

multi
|> Multi.run(:insert_staking_pools, fn repo, _ ->
insert(repo, changes_list, insert_options)
end)
end

@impl Import.Runner
def timeout, do: @timeout

@spec insert(Repo.t(), [map()], %{
optional(:on_conflict) => Import.Runner.on_conflict(),
required(:timeout) => timeout,
required(:timestamps) => Import.timestamps()
}) ::
{:ok, [Address.Name.t()]}
| {:error, [Changeset.t()]}
defp insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do
on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0)

{:ok, _} =
Import.insert_changes_list(
repo,
changes_list,
conflict_target: {:unsafe_fragment, "(address_hash) where \"primary\" = true"},
on_conflict: on_conflict,
for: Address.Name,
returning: [:address_hash],
timeout: timeout,
timestamps: timestamps
)
end

defp default_on_conflict do
from(
name in Address.Name,
update: [
set: [
name: fragment("EXCLUDED.name"),
metadata: fragment("EXCLUDED.metadata"),
inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", name.inserted_at),
updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", name.updated_at)
]
]
)
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ defmodule Explorer.Chain.Import.Stage.AddressReferencing do
Runner.Tokens,
Runner.TokenTransfers,
Runner.Address.CurrentTokenBalances,
Runner.Address.TokenBalances
Runner.Address.TokenBalances,
Runner.StakingPools
]

@impl Stage
Expand Down
121 changes: 121 additions & 0 deletions apps/explorer/lib/explorer/staking/pools_reader.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
defmodule Explorer.Staking.PoolsReader do
@moduledoc """
Reads staking pools using Smart Contract functions from the blockchain.
"""
alias Explorer.SmartContract.Reader

@spec get_pools() :: [String.t()]
def get_pools do
get_active_pools() ++ get_inactive_pools()
end

@spec get_active_pools() :: [String.t()]
def get_active_pools do
{:ok, [active_pools]} = call_staking_method("getPools", [])
active_pools
end

@spec get_inactive_pools() :: [String.t()]
def get_inactive_pools do
{:ok, [inactive_pools]} = call_staking_method("getPoolsInactive", [])
inactive_pools
end

@spec pool_data(String.t()) :: {:ok, map()} | :error
def pool_data(staking_address) do
with {:ok, [mining_address]} <- call_validators_method("miningByStakingAddress", [staking_address]),
data = fetch_data(staking_address, mining_address),
{:ok, [is_active]} <- data["isPoolActive"],
{:ok, [delegator_addresses]} <- data["poolDelegators"],
delegators_count = Enum.count(delegator_addresses),
{:ok, [staked_amount]} <- data["stakeAmountTotalMinusOrderedWithdraw"],
{:ok, [is_validator]} <- data["isValidator"],
{:ok, [was_validator_count]} <- data["validatorCounter"],
{:ok, [is_banned]} <- data["isValidatorBanned"],
{:ok, [banned_until]} <- data["bannedUntil"],
{:ok, [was_banned_count]} <- data["banCounter"] do
{
:ok,
%{
staking_address: staking_address,
mining_address: mining_address,
is_active: is_active,
delegators_count: delegators_count,
staked_amount: staked_amount,
is_validator: is_validator,
was_validator_count: was_validator_count,
is_banned: is_banned,
banned_until: banned_until,
was_banned_count: was_banned_count
}
}
else
_ ->
:error
end
end

defp call_staking_method(method, params) do
%{^method => resp} =
Reader.query_contract(config(:staking_contract_address), abi("staking.json"), %{
method => params
})

resp
end

defp call_validators_method(method, params) do
%{^method => resp} =
Reader.query_contract(config(:validators_contract_address), abi("validators.json"), %{
method => params
})

resp
end

defp fetch_data(staking_address, mining_address) 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}
]

methods
|> Enum.map(&format_request/1)
|> Reader.query_contracts(contract_abi)
|> Enum.zip(methods)
|> Enum.into(%{}, fn {response, {_, function_name, _}} ->
{function_name, response}
end)
end

defp format_request({contract_name, function_name, param}) do
%{
contract_address: contract(contract_name),
function_name: function_name,
args: [param]
}
end

defp contract(:staking), do: config(:staking_contract_address)
defp contract(:validators), do: config(:validators_contract_address)

defp config(key) do
Application.get_env(:explorer, __MODULE__, [])[key]
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
2 changes: 1 addition & 1 deletion apps/explorer/lib/explorer/validator/metadata_retriever.ex
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ defmodule Explorer.Validator.MetadataRetriever do
# sobelow_skip ["Traversal"]
defp contract_abi(file_name) do
:explorer
|> Application.app_dir("priv/validator_contracts_abi/#{file_name}")
|> Application.app_dir("priv/contracts_abi/poa/#{file_name}")
|> File.read!()
|> Jason.decode!()
end
Expand Down
Loading

0 comments on commit 2d64421

Please sign in to comment.