Merge pull request #1801 from poanetwork/pools_fetching

Staking pools fetching
pull/1896/head
Victor Baranov 6 years ago committed by GitHub
commit 2d644214ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 4
      apps/explorer/config/config.exs
  3. 88
      apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex
  4. 3
      apps/explorer/lib/explorer/chain/import/stage/address_referencing.ex
  5. 121
      apps/explorer/lib/explorer/staking/pools_reader.ex
  6. 2
      apps/explorer/lib/explorer/validator/metadata_retriever.ex
  7. 0
      apps/explorer/priv/contracts_abi/poa/metadata.json
  8. 0
      apps/explorer/priv/contracts_abi/poa/validators.json
  9. 925
      apps/explorer/priv/contracts_abi/pos/staking.json
  10. 492
      apps/explorer/priv/contracts_abi/pos/validators.json
  11. 94
      apps/explorer/test/explorer/chain/import/runner/staking_pools_test.exs
  12. 238
      apps/explorer/test/explorer/staking/pools_reader_test.exs
  13. 2
      apps/indexer/README.md
  14. 1
      apps/indexer/config/config.exs
  15. 5
      apps/indexer/lib/indexer/block/fetcher.ex
  16. 4
      apps/indexer/lib/indexer/block/realtime/fetcher.ex
  17. 136
      apps/indexer/lib/indexer/fetcher/staking_pools.ex
  18. 2
      apps/indexer/lib/indexer/supervisor.ex
  19. 205
      apps/indexer/test/indexer/fetcher/staking_pools_test.exs

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -0,0 +1,925 @@
[
{
"constant": true,
"inputs": [],
"name": "STAKE_UNIT",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "MAX_DELEGATORS_PER_POOL",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "MAX_CANDIDATES",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"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,
"inputs": [
{
"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": "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"
},
{
"indexed": false,
"name": "amount",
"type": "uint256"
}
],
"name": "StakeMoved",
"type": "event"
},
{
"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": "int256"
}
],
"name": "WithdrawalOrdered",
"type": "event"
},
{
"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": "Withdrawn",
"type": "event"
},
{
"constant": false,
"inputs": [
{
"name": "_unremovableStakingAddress",
"type": "address"
}
],
"name": "clearUnremovableValidator",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "incrementStakingEpoch",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_stakingAddress",
"type": "address"
}
],
"name": "removePool",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "removePool",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_fromPoolStakingAddress",
"type": "address"
},
{
"name": "_toPoolStakingAddress",
"type": "address"
},
{
"name": "_amount",
"type": "uint256"
}
],
"name": "moveStake",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_toPoolStakingAddress",
"type": "address"
},
{
"name": "_amount",
"type": "uint256"
}
],
"name": "stake",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_fromPoolStakingAddress",
"type": "address"
},
{
"name": "_amount",
"type": "uint256"
}
],
"name": "withdraw",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_poolStakingAddress",
"type": "address"
},
{
"name": "_amount",
"type": "int256"
}
],
"name": "orderWithdraw",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_poolStakingAddress",
"type": "address"
}
],
"name": "claimOrderedWithdraw",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_erc20TokenContract",
"type": "address"
}
],
"name": "setErc20TokenContract",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_minStake",
"type": "uint256"
}
],
"name": "setCandidateMinStake",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_minStake",
"type": "uint256"
}
],
"name": "setDelegatorMinStake",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getPools",
"outputs": [
{
"name": "",
"type": "address[]"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getPoolsInactive",
"outputs": [
{
"name": "",
"type": "address[]"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getPoolsLikelihood",
"outputs": [
{
"name": "likelihoods",
"type": "int256[]"
},
{
"name": "sum",
"type": "int256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getPoolsToBeElected",
"outputs": [
{
"name": "",
"type": "address[]"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getPoolsToBeRemoved",
"outputs": [
{
"name": "",
"type": "address[]"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "areStakeAndWithdrawAllowed",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "erc20TokenContract",
"outputs": [
{
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getCandidateMinStake",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getDelegatorMinStake",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_stakingAddress",
"type": "address"
}
],
"name": "isPoolActive",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_poolStakingAddress",
"type": "address"
},
{
"name": "_staker",
"type": "address"
}
],
"name": "maxWithdrawAllowed",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_poolStakingAddress",
"type": "address"
},
{
"name": "_staker",
"type": "address"
}
],
"name": "maxWithdrawOrderAllowed",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "",
"type": "address"
},
{
"name": "",
"type": "uint256"
},
{
"name": "",
"type": "bytes"
}
],
"name": "onTokenTransfer",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "pure",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_poolStakingAddress",
"type": "address"
},
{
"name": "_staker",
"type": "address"
}
],
"name": "orderedWithdrawAmount",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_poolStakingAddress",
"type": "address"
}
],
"name": "orderedWithdrawAmountTotal",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_poolStakingAddress",
"type": "address"
},
{
"name": "_staker",
"type": "address"
}
],
"name": "orderWithdrawEpoch",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_poolStakingAddress",
"type": "address"
}
],
"name": "stakeAmountTotal",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_poolStakingAddress",
"type": "address"
}
],
"name": "poolDelegators",
"outputs": [
{
"name": "",
"type": "address[]"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_poolStakingAddress",
"type": "address"
},
{
"name": "_delegator",
"type": "address"
}
],
"name": "poolDelegatorIndex",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_poolStakingAddress",
"type": "address"
},
{
"name": "_delegator",
"type": "address"
}
],
"name": "poolDelegatorInactiveIndex",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_stakingAddress",
"type": "address"
}
],
"name": "poolIndex",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_stakingAddress",
"type": "address"
}
],
"name": "poolInactiveIndex",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_stakingAddress",
"type": "address"
}
],
"name": "poolToBeElectedIndex",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_stakingAddress",
"type": "address"
}
],
"name": "poolToBeRemovedIndex",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_poolStakingAddress",
"type": "address"
},
{
"name": "_staker",
"type": "address"
}
],
"name": "stakeAmount",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_poolStakingAddress",
"type": "address"
},
{
"name": "_staker",
"type": "address"
}
],
"name": "stakeAmountByCurrentEpoch",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_poolStakingAddress",
"type": "address"
},
{
"name": "_staker",
"type": "address"
}
],
"name": "stakeAmountMinusOrderedWithdraw",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_poolStakingAddress",
"type": "address"
}
],
"name": "stakeAmountTotalMinusOrderedWithdraw",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "stakingEpoch",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "validatorSetContract",
"outputs": [
{
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
}
]

@ -0,0 +1,492 @@
[
{
"constant": false,
"inputs": [],
"name": "newValidatorSet",
"outputs": [
{
"name": "",
"type": "bool"
},
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "MAX_VALIDATORS",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"indexed": true,
"name": "parentHash",
"type": "bytes32"
},
{
"indexed": false,
"name": "newSet",
"type": "address[]"
}
],
"name": "InitiateChange",
"type": "event",
"anonymous": false
},
{
"inputs": [],
"name": "clearUnremovableValidator",
"type": "function",
"constant": false,
"outputs": [],
"payable": false,
"stateMutability": "nonpayable"
},
{
"inputs": [],
"name": "emitInitiateChange",
"type": "function",
"constant": false,
"outputs": [],
"payable": false,
"stateMutability": "nonpayable"
},
{
"inputs": [],
"name": "finalizeChange",
"type": "function",
"constant": false,
"outputs": [],
"payable": false,
"stateMutability": "nonpayable"
},
{
"inputs": [
{
"name": "_blockRewardContract",
"type": "address"
},
{
"name": "_randomContract",
"type": "address"
},
{
"name": "_stakingContract",
"type": "address"
},
{
"name": "_initialMiningAddresses",
"type": "address[]"
},
{
"name": "_initialStakingAddresses",
"type": "address[]"
},
{
"name": "_firstValidatorIsUnremovable",
"type": "bool"
}
],
"name": "initialize",
"type": "function",
"constant": false,
"outputs": [],
"payable": false,
"stateMutability": "nonpayable"
},
{
"inputs": [
{
"name": "_miningAddress",
"type": "address"
},
{
"name": "_stakingAddress",
"type": "address"
}
],
"name": "setStakingAddress",
"type": "function",
"constant": false,
"outputs": [],
"payable": false,
"stateMutability": "nonpayable"
},
{
"constant": true,
"inputs": [
{
"name": "_miningAddress",
"type": "address"
}
],
"name": "banCounter",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_miningAddress",
"type": "address"
}
],
"name": "bannedUntil",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "blockRewardContract",
"outputs": [
{
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "changeRequestCount",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "emitInitiateChangeCallable",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getPreviousValidators",
"outputs": [
{
"name": "",
"type": "address[]"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getPendingValidators",
"outputs": [
{
"name": "",
"type": "address[]"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getQueueValidators",
"outputs": [
{
"name": "",
"type": "address[]"
},
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getValidators",
"outputs": [
{
"name": "",
"type": "address[]"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "initiateChangeAllowed",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_miningAddress",
"type": "address"
}
],
"name": "isReportValidatorValid",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_miningAddress",
"type": "address"
}
],
"name": "isValidator",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_miningAddress",
"type": "address"
}
],
"name": "isValidatorOnPreviousEpoch",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_miningAddress",
"type": "address"
}
],
"name": "isValidatorBanned",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_stakingAddress",
"type": "address"
}
],
"name": "miningByStakingAddress",
"outputs": [
{
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "randomContract",
"outputs": [
{
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_miningAddress",
"type": "address"
}
],
"name": "stakingByMiningAddress",
"outputs": [
{
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "stakingContract",
"outputs": [
{
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "unremovableValidator",
"outputs": [
{
"name": "stakingAddress",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_miningAddress",
"type": "address"
}
],
"name": "validatorCounter",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_miningAddress",
"type": "address"
}
],
"name": "validatorIndex",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "validatorSetApplyBlock",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
}
]

@ -0,0 +1,94 @@
defmodule Explorer.Chain.Import.Runner.StakingPoolsTest do
use Explorer.DataCase
alias Ecto.Multi
alias Explorer.Chain.Import.Runner.StakingPools
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{
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: 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: <<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: 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: <<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)
end
end
defp run_changes(changes) do
Multi.new()
|> StakingPools.run(changes, %{
timeout: :infinity,
timestamps: %{inserted_at: DateTime.utc_now(), updated_at: DateTime.utc_now()}
})
|> Repo.transaction()
end
end

@ -0,0 +1,238 @@
defmodule Explorer.Token.PoolsReaderTest do
use EthereumJSONRPC.Case
use Explorer.DataCase
alias Explorer.Staking.PoolsReader
import Mox
setup :verify_on_exit!
setup :set_mox_global
describe "get_pools_list" do
test "get_active_pools success" do
get_pools_from_blockchain()
result = PoolsReader.get_active_pools()
assert Enum.count(result) == 3
end
test "get_active_pools error" do
fetch_from_blockchain_with_error()
assert_raise MatchError, fn ->
PoolsReader.get_active_pools()
end
end
end
describe "get_pools_data" do
test "get_pool_data success" do
get_pool_data_from_blockchain()
address = <<11, 47, 94, 47, 60, 189, 134, 78, 170, 44, 100, 46, 55, 105, 193, 88, 35, 97, 202, 246>>
response = {
:ok,
%{
banned_until: 0,
delegators_count: 0,
is_active: true,
is_banned: false,
is_validator: true,
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,
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
}
}
assert PoolsReader.pool_data(address) == response
end
test "get_pool_data error" do
fetch_from_blockchain_with_error()
address = <<11, 47, 94, 47, 60, 189, 134, 78, 170, 44, 100, 46, 55, 105, 193, 88, 35, 97, 202, 246>>
assert :error = PoolsReader.pool_data(address)
end
end
defp get_pools_from_blockchain() do
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn [%{id: id, method: "eth_call", params: _}], _options ->
{:ok,
[
%{
id: id,
jsonrpc: "2.0",
result:
"0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6000000000000000000000000aa94b687d3f9552a453b81b2834ca53778980dc0000000000000000000000000312c230e7d6db05224f60208a656e3541c5c42ba"
}
]}
end
)
end
defp fetch_from_blockchain_with_error() do
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn [%{id: id, method: "eth_call", params: _}], _options ->
{:ok,
[
%{
error: %{code: -32015, data: "Reverted 0x", message: "VM execution error."},
id: id,
jsonrpc: "2.0"
}
]}
end
)
end
defp get_pool_data_from_blockchain() do
expect(
EthereumJSONRPC.Mox,
:json_rpc,
2,
fn requests, _opts ->
{:ok,
Enum.map(requests, fn
# miningByStakingAddress
%{
id: id,
method: "eth_call",
params: [
%{data: "0x005351750000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _},
"latest"
]
} ->
%{
id: id,
result: "0x000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78"
}
# isPoolActive
%{
id: id,
method: "eth_call",
params: [
%{data: "0xa711e6a10000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _},
"latest"
]
} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000000000000000001"
}
# poolDelegators
%{
id: id,
method: "eth_call",
params: [
%{data: "0x9ea8082b0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _},
"latest"
]
} ->
%{
id: id,
result:
"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000"
}
# stakeAmountTotalMinusOrderedWithdraw
%{
id: id,
method: "eth_call",
params: [
%{data: "0x234fbf2b0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _},
"latest"
]
} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000000000000000000"
}
# isValidator
%{
id: id,
method: "eth_call",
params: [
%{data: "0xfacd743b000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _},
"latest"
]
} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000000000000000001"
}
# validatorCounter
%{
id: id,
method: "eth_call",
params: [
%{data: "0xb41832e4000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _},
"latest"
]
} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000000000000000002"
}
# isValidatorBanned
%{
id: id,
method: "eth_call",
params: [
%{data: "0xa92252ae000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _},
"latest"
]
} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000000000000000000"
}
# bannedUntil
%{
id: id,
method: "eth_call",
params: [
%{data: "0x5836d08a000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _},
"latest"
]
} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000000000000000000"
}
# banCounter
%{
id: id,
method: "eth_call",
params: [
%{data: "0x1d0cd4c6000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _},
"latest"
]
} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000000000000000000"
}
end)}
end
)
end
end

@ -57,6 +57,7 @@ The following async fetchers are launched for importing missing data:
- `token_balance`
- `token`
- `contract_code`
- `staking_pools`
### Async fetchers
@ -78,6 +79,7 @@ Most of them are based off `BufferedTask`, and the basic algorithm goes like thi
- `token_balance`: for `address_token_balances` with null `value_fetched_at`. Also upserts `address_current_token_balances`
- `token`: for `tokens` with `cataloged == false`
- `contract_code`: for `transactions` with non-null `created_contract_address_hash` and null `created_contract_code_indexed_at`
- `staking_pools`: for fetching staking pools
Additionally:
- `token_updater` is run every 2 days to update token metadata

@ -38,6 +38,7 @@ config :indexer,
# config :indexer, Indexer.Fetcher.ReplacedTransaction.Supervisor, disabled?: true
# config :indexer, Indexer.Fetcher.BlockReward.Supervisor, disabled?: true
config :indexer, Indexer.Fetcher.StakingPools.Supervisor, disabled?: true
config :indexer, Indexer.Tracer,
service: :indexer,

@ -20,6 +20,7 @@ defmodule Indexer.Block.Fetcher do
ContractCode,
InternalTransaction,
ReplacedTransaction,
StakingPools,
Token,
TokenBalance,
UncleBlock
@ -280,6 +281,10 @@ defmodule Indexer.Block.Fetcher do
def async_import_token_balances(_), do: :ok
def async_import_staking_pools do
StakingPools.async_fetch()
end
def async_import_uncles(%{block_second_degree_relations: block_second_degree_relations}) do
UncleBlock.async_fetch_blocks(block_second_degree_relations)
end

@ -20,7 +20,8 @@ defmodule Indexer.Block.Realtime.Fetcher do
async_import_tokens: 1,
async_import_token_balances: 1,
async_import_uncles: 1,
fetch_and_import_range: 2
fetch_and_import_range: 2,
async_import_staking_pools: 0
]
alias Ecto.Changeset
@ -350,6 +351,7 @@ defmodule Indexer.Block.Realtime.Fetcher do
async_import_token_balances(imported)
async_import_uncles(imported)
async_import_replaced_transactions(imported)
async_import_staking_pools()
end
defp balances(

@ -0,0 +1,136 @@
defmodule Indexer.Fetcher.StakingPools do
@moduledoc """
Fetches staking pools and send to be imported in `Address.Name` table
"""
use Indexer.Fetcher
use Spandex.Decorators
require Logger
alias Explorer.Chain
alias Explorer.Staking.PoolsReader
alias Indexer.BufferedTask
alias Indexer.Fetcher.StakingPools.Supervisor, as: StakingPoolsSupervisor
@behaviour BufferedTask
@defaults [
flush_interval: 300,
max_batch_size: 100,
max_concurrency: 10,
task_supervisor: Indexer.Fetcher.StakingPools.TaskSupervisor
]
@max_retries 3
@spec async_fetch() :: :ok
def async_fetch do
if StakingPoolsSupervisor.disabled?() do
:ok
else
pools =
PoolsReader.get_pools()
|> Enum.map(&entry/1)
BufferedTask.buffer(__MODULE__, pools, :infinity)
end
end
@doc false
def child_spec([init_options, gen_server_options]) do
merged_init_opts =
@defaults
|> Keyword.merge(init_options)
|> Keyword.put(:state, {0, []})
Supervisor.child_spec({BufferedTask, [{__MODULE__, merged_init_opts}, gen_server_options]}, id: __MODULE__)
end
@impl BufferedTask
def init(_initial, reducer, acc) do
PoolsReader.get_pools()
|> Enum.map(&entry/1)
|> Enum.reduce(acc, &reducer.(&1, &2))
end
@impl BufferedTask
def run(pools, _json_rpc_named_arguments) do
failed_list =
pools
|> Enum.map(&Map.put(&1, :retries_count, &1.retries_count + 1))
|> fetch_from_blockchain()
|> import_pools()
if failed_list == [] do
:ok
else
{:retry, failed_list}
end
end
def entry(pool_address) do
%{
staking_address: pool_address,
retries_count: 0
}
end
defp fetch_from_blockchain(addresses) do
addresses
|> Enum.filter(&(&1.retries_count <= @max_retries))
|> Enum.map(fn %{staking_address: staking_address} = pool ->
case PoolsReader.pool_data(staking_address) do
{:ok, data} ->
Map.merge(pool, data)
error ->
Map.put(pool, :error, error)
end
end)
end
defp import_pools(pools) do
{failed, success} =
Enum.reduce(pools, {[], []}, fn
%{error: _error, staking_address: address}, {failed, success} ->
{[address | failed], success}
pool, {failed, success} ->
{failed, [changeset(pool) | success]}
end)
import_params = %{
staking_pools: %{params: success},
timeout: :infinity
}
case Chain.import(import_params) do
{:ok, _} ->
:ok
{:error, reason} ->
Logger.debug(fn -> ["failed to import staking pools: ", inspect(reason)] end,
error_count: Enum.count(pools)
)
end
failed
end
defp changeset(%{staking_address: staking_address} = pool) do
{:ok, mining_address} = Chain.Hash.Address.cast(pool[:mining_address])
data =
pool
|> Map.delete(:staking_address)
|> Map.put(:mining_address, mining_address)
%{
name: "anonymous",
primary: true,
address_hash: staking_address,
metadata: data
}
end
end

@ -16,6 +16,7 @@ defmodule Indexer.Supervisor do
InternalTransaction,
PendingTransaction,
ReplacedTransaction,
StakingPools,
Token,
TokenBalance,
TokenUpdater,
@ -122,6 +123,7 @@ defmodule Indexer.Supervisor do
{TokenBalance.Supervisor,
[[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]},
{ReplacedTransaction.Supervisor, [[memory_monitor: memory_monitor]]},
{StakingPools.Supervisor, [[memory_monitor: memory_monitor]]},
# Out-of-band fetchers
{CoinBalanceOnDemand.Supervisor, [json_rpc_named_arguments]},

@ -0,0 +1,205 @@
defmodule Indexer.Fetcher.StakingPoolsTest do
use EthereumJSONRPC.Case
use Explorer.DataCase
import Mox
alias Indexer.Fetcher.StakingPools
alias Explorer.Staking.PoolsReader
alias Explorer.Chain.Address
@moduletag :capture_log
setup :verify_on_exit!
describe "init/3" do
test "returns pools addresses" do
get_pools_from_blockchain(2)
list = StakingPools.init([], &[&1 | &2], [])
assert Enum.count(list) == 6
end
end
describe "run/3" do
test "one success import from pools" do
get_pools_from_blockchain(1)
list =
PoolsReader.get_active_pools()
|> Enum.map(&StakingPools.entry/1)
success_address =
list
|> List.first()
|> Map.get(:staking_address)
get_pool_data_from_blockchain()
assert {:retry, retry_list} = StakingPools.run(list, nil)
assert Enum.count(retry_list) == 2
pool = Explorer.Repo.get_by(Address.Name, address_hash: success_address)
assert pool.name == "anonymous"
end
end
defp get_pools_from_blockchain(n) do
expect(
EthereumJSONRPC.Mox,
:json_rpc,
n,
fn [%{id: id, method: "eth_call", params: _}], _options ->
{:ok,
[
%{
id: id,
jsonrpc: "2.0",
result:
"0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6000000000000000000000000aa94b687d3f9552a453b81b2834ca53778980dc0000000000000000000000000312c230e7d6db05224f60208a656e3541c5c42ba"
}
]}
end
)
end
defp get_pool_data_from_blockchain() do
expect(
EthereumJSONRPC.Mox,
:json_rpc,
4,
fn requests, _opts ->
{:ok,
Enum.map(requests, fn
# miningByStakingAddress
%{
id: id,
method: "eth_call",
params: [
%{data: "0x005351750000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _},
"latest"
]
} ->
%{
id: id,
result: "0x000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78"
}
# isPoolActive
%{
id: id,
method: "eth_call",
params: [
%{data: "0xa711e6a10000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _},
"latest"
]
} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000000000000000001"
}
# poolDelegators
%{
id: id,
method: "eth_call",
params: [
%{data: "0x9ea8082b0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _},
"latest"
]
} ->
%{
id: id,
result:
"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000"
}
# stakeAmountTotalMinusOrderedWithdraw
%{
id: id,
method: "eth_call",
params: [
%{data: "0x234fbf2b0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _},
"latest"
]
} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000000000000000000"
}
# isValidator
%{
id: id,
method: "eth_call",
params: [
%{data: "0xfacd743b000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _},
"latest"
]
} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000000000000000001"
}
# validatorCounter
%{
id: id,
method: "eth_call",
params: [
%{data: "0xb41832e4000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _},
"latest"
]
} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000000000000000002"
}
# isValidatorBanned
%{
id: id,
method: "eth_call",
params: [
%{data: "0xa92252ae000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _},
"latest"
]
} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000000000000000000"
}
# bannedUntil
%{
id: id,
method: "eth_call",
params: [
%{data: "0x5836d08a000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _},
"latest"
]
} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000000000000000000"
}
# banCounter
%{
id: id,
method: "eth_call",
params: [
%{data: "0x1d0cd4c6000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _},
"latest"
]
} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000000000000000000"
}
end)}
end
)
end
end
Loading…
Cancel
Save