feat: Add Blackfort validators (#10744)
* feat: Add Blackfort validators * Finishing touches * Add missing specs, docs, pre-release workflow for Blackfort * Remove blocks_validated --------- Co-authored-by: Victor Baranov <baranov.viktor.27@gmail.com>pull/10739/head
parent
f7a434df17
commit
629620bc52
@ -0,0 +1,97 @@ |
||||
name: Pre-release for Blackfort |
||||
|
||||
on: |
||||
workflow_dispatch: |
||||
inputs: |
||||
number: |
||||
type: number |
||||
required: true |
||||
|
||||
env: |
||||
OTP_VERSION: ${{ vars.OTP_VERSION }} |
||||
ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} |
||||
|
||||
jobs: |
||||
push_to_registry: |
||||
name: Push Docker image to Docker Hub |
||||
runs-on: ubuntu-latest |
||||
env: |
||||
RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} |
||||
steps: |
||||
- uses: actions/checkout@v4 |
||||
- name: Setup repo |
||||
uses: ./.github/actions/setup-repo |
||||
id: setup |
||||
with: |
||||
docker-username: ${{ secrets.DOCKER_USERNAME }} |
||||
docker-password: ${{ secrets.DOCKER_PASSWORD }} |
||||
docker-remote-multi-platform: true |
||||
docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} |
||||
docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} |
||||
|
||||
- name: Build and push Docker image for Blackfort (indexer + API) |
||||
uses: docker/build-push-action@v5 |
||||
with: |
||||
context: . |
||||
file: ./docker/Dockerfile |
||||
push: true |
||||
tags: blockscout/blockscout-blackfort:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} |
||||
labels: ${{ steps.setup.outputs.docker-labels }} |
||||
platforms: | |
||||
linux/amd64 |
||||
linux/arm64/v8 |
||||
build-args: | |
||||
DISABLE_WEBAPP=false |
||||
API_V1_READ_METHODS_DISABLED=false |
||||
API_V1_WRITE_METHODS_DISABLED=false |
||||
CACHE_EXCHANGE_RATES_PERIOD= |
||||
CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= |
||||
CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= |
||||
ADMIN_PANEL_ENABLED=false |
||||
BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} |
||||
RELEASE_VERSION=${{ env.RELEASE_VERSION }} |
||||
CHAIN_TYPE=blackfort |
||||
|
||||
- name: Build and push Docker image for Blackfort (indexer) |
||||
uses: docker/build-push-action@v5 |
||||
with: |
||||
context: . |
||||
file: ./docker/Dockerfile |
||||
push: true |
||||
tags: blockscout/blockscout-blackfort:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-indexer |
||||
labels: ${{ steps.setup.outputs.docker-labels }} |
||||
platforms: | |
||||
linux/amd64 |
||||
linux/arm64/v8 |
||||
build-args: | |
||||
DISABLE_API=true |
||||
DISABLE_WEBAPP=true |
||||
CACHE_EXCHANGE_RATES_PERIOD= |
||||
CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= |
||||
CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= |
||||
ADMIN_PANEL_ENABLED=false |
||||
BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} |
||||
RELEASE_VERSION=${{ env.RELEASE_VERSION }} |
||||
CHAIN_TYPE=blackfort |
||||
|
||||
- name: Build and push Docker image for Blackfort (API) |
||||
uses: docker/build-push-action@v5 |
||||
with: |
||||
context: . |
||||
file: ./docker/Dockerfile |
||||
push: true |
||||
tags: blockscout/blockscout-blackfort:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-api |
||||
labels: ${{ steps.setup.outputs.docker-labels }} |
||||
platforms: | |
||||
linux/amd64 |
||||
linux/arm64/v8 |
||||
build-args: | |
||||
DISABLE_INDEXER=true |
||||
DISABLE_WEBAPP=true |
||||
CACHE_EXCHANGE_RATES_PERIOD= |
||||
CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= |
||||
CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= |
||||
ADMIN_PANEL_ENABLED=false |
||||
BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} |
||||
RELEASE_VERSION=${{ env.RELEASE_VERSION }} |
||||
CHAIN_TYPE=blackfort |
@ -0,0 +1,51 @@ |
||||
name: Blackfort Publish Docker image |
||||
|
||||
on: |
||||
workflow_dispatch: |
||||
push: |
||||
branches: |
||||
- production-blackfort |
||||
env: |
||||
OTP_VERSION: ${{ vars.OTP_VERSION }} |
||||
ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} |
||||
jobs: |
||||
push_to_registry: |
||||
name: Push Docker image to Docker Hub |
||||
runs-on: ubuntu-latest |
||||
env: |
||||
RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} |
||||
DOCKER_CHAIN_NAME: blackfort |
||||
steps: |
||||
- uses: actions/checkout@v4 |
||||
- name: Setup repo |
||||
uses: ./.github/actions/setup-repo |
||||
id: setup |
||||
with: |
||||
docker-username: ${{ secrets.DOCKER_USERNAME }} |
||||
docker-password: ${{ secrets.DOCKER_PASSWORD }} |
||||
docker-remote-multi-platform: true |
||||
docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} |
||||
docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} |
||||
|
||||
- name: Build and push Docker image |
||||
uses: docker/build-push-action@v5 |
||||
with: |
||||
context: . |
||||
file: ./docker/Dockerfile |
||||
push: true |
||||
tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }} |
||||
labels: ${{ steps.setup.outputs.docker-labels }} |
||||
platforms: | |
||||
linux/amd64 |
||||
linux/arm64/v8 |
||||
build-args: | |
||||
CACHE_EXCHANGE_RATES_PERIOD= |
||||
API_V1_READ_METHODS_DISABLED=false |
||||
DISABLE_WEBAPP=false |
||||
API_V1_WRITE_METHODS_DISABLED=false |
||||
CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= |
||||
ADMIN_PANEL_ENABLED=false |
||||
CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= |
||||
BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }} |
||||
RELEASE_VERSION=${{ env.RELEASE_VERSION }} |
||||
CHAIN_TYPE=blackfort |
@ -0,0 +1,94 @@ |
||||
name: Release for Blackfort |
||||
|
||||
on: |
||||
release: |
||||
types: [published] |
||||
|
||||
env: |
||||
OTP_VERSION: ${{ vars.OTP_VERSION }} |
||||
ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} |
||||
|
||||
jobs: |
||||
push_to_registry: |
||||
name: Push Docker image to Docker Hub |
||||
runs-on: ubuntu-latest |
||||
env: |
||||
RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} |
||||
steps: |
||||
- uses: actions/checkout@v4 |
||||
- name: Setup repo |
||||
uses: ./.github/actions/setup-repo |
||||
id: setup |
||||
with: |
||||
docker-username: ${{ secrets.DOCKER_USERNAME }} |
||||
docker-password: ${{ secrets.DOCKER_PASSWORD }} |
||||
docker-remote-multi-platform: true |
||||
docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} |
||||
docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} |
||||
|
||||
- name: Build and push Docker image for Blackfort (indexer + API) |
||||
uses: docker/build-push-action@v5 |
||||
with: |
||||
context: . |
||||
file: ./docker/Dockerfile |
||||
push: true |
||||
tags: blockscout/blockscout-blackfort:latest, blockscout/blockscout-blackfort:${{ env.RELEASE_VERSION }} |
||||
labels: ${{ steps.setup.outputs.docker-labels }} |
||||
platforms: | |
||||
linux/amd64 |
||||
linux/arm64/v8 |
||||
build-args: | |
||||
DISABLE_WEBAPP=false |
||||
API_V1_READ_METHODS_DISABLED=false |
||||
API_V1_WRITE_METHODS_DISABLED=false |
||||
CACHE_EXCHANGE_RATES_PERIOD= |
||||
CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= |
||||
CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= |
||||
ADMIN_PANEL_ENABLED=false |
||||
BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta |
||||
RELEASE_VERSION=${{ env.RELEASE_VERSION }} |
||||
CHAIN_TYPE=blackfort |
||||
|
||||
- name: Build and push Docker image for Blackfort (indexer) |
||||
uses: docker/build-push-action@v5 |
||||
with: |
||||
context: . |
||||
file: ./docker/Dockerfile |
||||
push: true |
||||
tags: blockscout/blockscout-blackfort:${{ env.RELEASE_VERSION }}-indexer |
||||
labels: ${{ steps.setup.outputs.docker-labels }} |
||||
platforms: | |
||||
linux/amd64 |
||||
linux/arm64/v8 |
||||
build-args: | |
||||
DISABLE_API=true |
||||
DISABLE_WEBAPP=true |
||||
CACHE_EXCHANGE_RATES_PERIOD= |
||||
CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= |
||||
CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= |
||||
ADMIN_PANEL_ENABLED=false |
||||
BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta |
||||
RELEASE_VERSION=${{ env.RELEASE_VERSION }} |
||||
CHAIN_TYPE=blackfort |
||||
|
||||
- name: Build and push Docker image for Blackfort (API) |
||||
uses: docker/build-push-action@v5 |
||||
with: |
||||
context: . |
||||
file: ./docker/Dockerfile |
||||
push: true |
||||
tags: blockscout/blockscout-blackfort:${{ env.RELEASE_VERSION }}-api |
||||
labels: ${{ steps.setup.outputs.docker-labels }} |
||||
platforms: | |
||||
linux/amd64 |
||||
linux/arm64/v8 |
||||
build-args: | |
||||
DISABLE_INDEXER=true |
||||
DISABLE_WEBAPP=true |
||||
CACHE_EXCHANGE_RATES_PERIOD= |
||||
CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= |
||||
CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= |
||||
ADMIN_PANEL_ENABLED=false |
||||
BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta |
||||
RELEASE_VERSION=${{ env.RELEASE_VERSION }} |
||||
CHAIN_TYPE=blackfort |
@ -0,0 +1,201 @@ |
||||
defmodule Explorer.Chain.Blackfort.Validator do |
||||
@moduledoc """ |
||||
Blackfort validators |
||||
""" |
||||
|
||||
use Explorer.Schema |
||||
|
||||
alias Explorer.Chain.{Address, Import} |
||||
alias Explorer.Chain.Hash.Address, as: HashAddress |
||||
alias Explorer.{Chain, Repo, SortingHelper} |
||||
|
||||
require Logger |
||||
|
||||
@default_sorting [ |
||||
asc: :address_hash |
||||
] |
||||
|
||||
@primary_key false |
||||
typed_schema "validators_blackfort" do |
||||
field(:address_hash, HashAddress, primary_key: true) |
||||
field(:name, :binary) |
||||
field(:commission, :integer) |
||||
field(:self_bonded_amount, :decimal) |
||||
field(:delegated_amount, :decimal) |
||||
field(:slashing_status_is_slashed, :boolean, default: false) |
||||
field(:slashing_status_by_block, :integer) |
||||
field(:slashing_status_multiplier, :integer) |
||||
|
||||
has_one(:address, Address, foreign_key: :hash, references: :address_hash) |
||||
timestamps() |
||||
end |
||||
|
||||
@required_attrs ~w(address_hash)a |
||||
@optional_attrs ~w(name commission self_bonded_amount delegated_amount)a |
||||
def changeset(%__MODULE__{} = validator, attrs) do |
||||
validator |
||||
|> cast(attrs, @required_attrs ++ @optional_attrs) |
||||
|> validate_required(@required_attrs) |
||||
|> unique_constraint(:address_hash) |
||||
end |
||||
|
||||
@doc """ |
||||
Get validators list. |
||||
Keyword could contain: |
||||
- paging_options |
||||
- necessity_by_association |
||||
- sorting (supported by `Explorer.SortingHelper` module) |
||||
- state (one of `@state_enum`) |
||||
""" |
||||
@spec get_paginated_validators(keyword()) :: [t()] |
||||
def get_paginated_validators(options \\ []) do |
||||
paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) |
||||
necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) |
||||
sorting = Keyword.get(options, :sorting, []) |
||||
|
||||
__MODULE__ |
||||
|> Chain.join_associations(necessity_by_association) |
||||
|> SortingHelper.apply_sorting(sorting, @default_sorting) |
||||
|> SortingHelper.page_with_sorting(paging_options, sorting, @default_sorting) |
||||
|> Chain.select_repo(options).all() |
||||
end |
||||
|
||||
@doc """ |
||||
Get all validators |
||||
""" |
||||
@spec get_all_validators(keyword()) :: [t()] |
||||
def get_all_validators(options \\ []) do |
||||
__MODULE__ |
||||
|> Chain.select_repo(options).all() |
||||
end |
||||
|
||||
@doc """ |
||||
Delete validators by address hashes |
||||
""" |
||||
@spec delete_validators_by_address_hashes([binary() | HashAddress.t()]) :: {non_neg_integer(), nil | []} | :ignore |
||||
def delete_validators_by_address_hashes(list) when is_list(list) and length(list) > 0 do |
||||
__MODULE__ |
||||
|> where([vs], vs.address_hash in ^list) |
||||
|> Repo.delete_all() |
||||
end |
||||
|
||||
def delete_validators_by_address_hashes(_), do: :ignore |
||||
|
||||
@doc """ |
||||
Insert validators |
||||
""" |
||||
@spec insert_validators([map()]) :: {non_neg_integer(), nil | []} |
||||
def insert_validators(validators) do |
||||
Repo.insert_all(__MODULE__, validators, |
||||
on_conflict: {:replace_all_except, [:inserted_at]}, |
||||
conflict_target: [:address_hash] |
||||
) |
||||
end |
||||
|
||||
@doc """ |
||||
Append timestamps (:inserted_at, :updated_at) |
||||
""" |
||||
@spec append_timestamps(map()) :: map() |
||||
def append_timestamps(validator) do |
||||
Map.merge(validator, Import.timestamps()) |
||||
end |
||||
|
||||
@doc """ |
||||
Derive next page params from %Explorer.Chain.Blackfort.Validator{} |
||||
""" |
||||
@spec next_page_params(t()) :: map() |
||||
def next_page_params(%__MODULE__{address_hash: address_hash}) do |
||||
%{"address_hash" => address_hash} |
||||
end |
||||
|
||||
@doc """ |
||||
Returns dynamic query for validated blocks count. Needed for SortingHelper |
||||
""" |
||||
@spec dynamic_validated_blocks() :: Ecto.Query.dynamic_expr() |
||||
def dynamic_validated_blocks do |
||||
dynamic( |
||||
[vs], |
||||
fragment( |
||||
"SELECT count(*) FROM blocks WHERE miner_hash = ?", |
||||
vs.address_hash |
||||
) |
||||
) |
||||
end |
||||
|
||||
@doc """ |
||||
Returns total count of validators. |
||||
""" |
||||
@spec count_validators() :: integer() |
||||
def count_validators do |
||||
Repo.aggregate(__MODULE__, :count, :address_hash) |
||||
end |
||||
|
||||
@doc """ |
||||
Returns count of new validators (inserted withing last 24h). |
||||
""" |
||||
@spec count_new_validators() :: integer() |
||||
def count_new_validators do |
||||
__MODULE__ |
||||
|> where([vs], vs.inserted_at >= ago(1, "day")) |
||||
|> Repo.aggregate(:count, :address_hash) |
||||
end |
||||
|
||||
@doc """ |
||||
Fetch list of Blackfort validators |
||||
""" |
||||
@spec fetch_validators_list() :: {:ok, list()} | :error |
||||
def fetch_validators_list do |
||||
case HTTPoison.get(validator_url(), [], follow_redirect: true) do |
||||
{:ok, %HTTPoison.Response{status_code: 200, body: body}} -> |
||||
body |> Jason.decode() |> parse_validators_info() |
||||
|
||||
error -> |
||||
Logger.error("Failed to fetch blackfort validator info: #{inspect(error)}") |
||||
:error |
||||
end |
||||
end |
||||
|
||||
defp parse_validators_info({:ok, validators}) do |
||||
{:ok, |
||||
validators |
||||
|> Enum.map(fn %{ |
||||
"address" => address_hash_string, |
||||
"name" => name, |
||||
"commission" => commission, |
||||
"self_bonded_amount" => self_bonded_amount, |
||||
"delegated_amount" => delegated_amount, |
||||
"slashing_status" => %{ |
||||
"is_slashed" => slashing_status_is_slashed, |
||||
"by_block" => slashing_status_by_block, |
||||
"multiplier" => slashing_status_multiplier |
||||
} |
||||
} -> |
||||
{:ok, address_hash} = HashAddress.cast(address_hash_string) |
||||
|
||||
%{ |
||||
address_hash: address_hash, |
||||
name: name, |
||||
commission: parse_number(commission), |
||||
self_bonded_amount: parse_number(self_bonded_amount), |
||||
delegated_amount: parse_number(delegated_amount), |
||||
slashing_status_is_slashed: slashing_status_is_slashed, |
||||
slashing_status_by_block: slashing_status_by_block, |
||||
slashing_status_multiplier: slashing_status_multiplier |
||||
} |
||||
end)} |
||||
end |
||||
|
||||
defp parse_validators_info({:error, error}) do |
||||
Logger.error("Failed to parse blackfort validator info: #{inspect(error)}") |
||||
:error |
||||
end |
||||
|
||||
defp validator_url do |
||||
Application.get_env(:explorer, __MODULE__)[:api_url] |
||||
end |
||||
|
||||
defp parse_number(string) do |
||||
{number, _} = Integer.parse(string) |
||||
number |
||||
end |
||||
end |
@ -0,0 +1,97 @@ |
||||
defmodule Explorer.Chain.Cache.BlackfortValidatorsCounters do |
||||
@moduledoc """ |
||||
Counts and store counters of validators blackfort. |
||||
|
||||
It loads the count asynchronously and in a time interval of 30 minutes. |
||||
""" |
||||
|
||||
use GenServer |
||||
|
||||
alias Explorer.Chain |
||||
alias Explorer.Chain.Blackfort.Validator, as: ValidatorBlackfort |
||||
|
||||
@validators_counter_key "blackfort_validators_counter" |
||||
@new_validators_counter_key "new_blackfort_validators_counter" |
||||
|
||||
# It is undesirable to automatically start the consolidation in all environments. |
||||
# Consider the test environment: if the consolidation initiates but does not |
||||
# finish before a test ends, that test will fail. This way, hundreds of |
||||
# tests were failing before disabling the consolidation and the scheduler in |
||||
# the test env. |
||||
config = Application.compile_env(:explorer, __MODULE__) |
||||
@enable_consolidation Keyword.get(config, :enable_consolidation) |
||||
|
||||
@update_interval_in_milliseconds Keyword.get(config, :update_interval_in_milliseconds) |
||||
|
||||
@doc """ |
||||
Starts a process to periodically update validators blackfort counters |
||||
""" |
||||
@spec start_link(term()) :: GenServer.on_start() |
||||
def start_link(_) do |
||||
GenServer.start_link(__MODULE__, [], name: __MODULE__) |
||||
end |
||||
|
||||
@impl true |
||||
def init(_args) do |
||||
{:ok, %{consolidate?: @enable_consolidation}, {:continue, :ok}} |
||||
end |
||||
|
||||
defp schedule_next_consolidation do |
||||
Process.send_after(self(), :consolidate, @update_interval_in_milliseconds) |
||||
end |
||||
|
||||
@impl true |
||||
def handle_continue(:ok, %{consolidate?: true} = state) do |
||||
consolidate() |
||||
schedule_next_consolidation() |
||||
|
||||
{:noreply, state} |
||||
end |
||||
|
||||
@impl true |
||||
def handle_continue(:ok, state) do |
||||
{:noreply, state} |
||||
end |
||||
|
||||
@impl true |
||||
def handle_info(:consolidate, state) do |
||||
consolidate() |
||||
schedule_next_consolidation() |
||||
|
||||
{:noreply, state} |
||||
end |
||||
|
||||
@doc """ |
||||
Fetches values for a blackfort validators counters from the `last_fetched_counters` table. |
||||
""" |
||||
@spec get_counters(Keyword.t()) :: map() |
||||
def get_counters(options) do |
||||
%{ |
||||
validators_counter: Chain.get_last_fetched_counter(@validators_counter_key, options), |
||||
new_validators_counter: Chain.get_last_fetched_counter(@new_validators_counter_key, options) |
||||
} |
||||
end |
||||
|
||||
@doc """ |
||||
Consolidates the info by populating the `last_fetched_counters` table with the current database information. |
||||
""" |
||||
@spec consolidate() :: any() |
||||
def consolidate do |
||||
tasks = [ |
||||
Task.async(fn -> ValidatorBlackfort.count_validators() end), |
||||
Task.async(fn -> ValidatorBlackfort.count_new_validators() end) |
||||
] |
||||
|
||||
[validators_counter, new_validators_counter] = Task.await_many(tasks, :infinity) |
||||
|
||||
Chain.upsert_last_fetched_counter(%{ |
||||
counter_type: @validators_counter_key, |
||||
value: validators_counter |
||||
}) |
||||
|
||||
Chain.upsert_last_fetched_counter(%{ |
||||
counter_type: @new_validators_counter_key, |
||||
value: new_validators_counter |
||||
}) |
||||
end |
||||
end |
@ -0,0 +1,20 @@ |
||||
defmodule Explorer.Repo.Blackfort.Migrations.AddBlackfortValidators do |
||||
use Ecto.Migration |
||||
|
||||
def change do |
||||
create table(:validators_blackfort, primary_key: false) do |
||||
add(:address_hash, :bytea, null: false, primary_key: true) |
||||
add(:name, :string) |
||||
add(:commission, :smallint) |
||||
add(:self_bonded_amount, :numeric, precision: 100) |
||||
add(:delegated_amount, :numeric, precision: 100) |
||||
add(:slashing_status_is_slashed, :boolean, default: false) |
||||
add(:slashing_status_by_block, :bigint) |
||||
add(:slashing_status_multiplier, :integer) |
||||
|
||||
timestamps() |
||||
end |
||||
|
||||
create_if_not_exists(index(:validators_blackfort, ["address_hash ASC"])) |
||||
end |
||||
end |
@ -0,0 +1,49 @@ |
||||
defmodule Indexer.Fetcher.Blackfort.Validator do |
||||
@moduledoc """ |
||||
GenServer responsible for updating the list of blackfort validators in the database. |
||||
""" |
||||
use GenServer |
||||
|
||||
alias Explorer.Chain.Blackfort.Validator |
||||
|
||||
def start_link(_) do |
||||
GenServer.start_link(__MODULE__, [], name: __MODULE__) |
||||
end |
||||
|
||||
@impl true |
||||
def init(state) do |
||||
GenServer.cast(__MODULE__, :update_validators_list) |
||||
|
||||
{:ok, state} |
||||
end |
||||
|
||||
@impl true |
||||
def handle_cast(:update_validators_list, state) do |
||||
case Validator.fetch_validators_list() do |
||||
{:ok, validators} -> |
||||
validators_from_db = Validator.get_all_validators() |
||||
|
||||
validators_map = |
||||
Enum.reduce(validators, %{}, fn %{address_hash: address_hash}, map -> |
||||
Map.put(map, address_hash.bytes, true) |
||||
end) |
||||
|
||||
address_hashes_to_drop_from_db = |
||||
Enum.flat_map(validators_from_db, fn validator -> |
||||
(is_nil(validators_map[validator.address_hash.bytes]) && |
||||
[validator.address_hash]) || [] |
||||
end) |
||||
|
||||
Validator.delete_validators_by_address_hashes(address_hashes_to_drop_from_db) |
||||
|
||||
validators |
||||
|> Enum.map(&Validator.append_timestamps/1) |
||||
|> Validator.insert_validators() |
||||
|
||||
_ -> |
||||
nil |
||||
end |
||||
|
||||
{:noreply, state} |
||||
end |
||||
end |
Loading…
Reference in new issue