Files
Dena/app/workers/daemons/blockchain.rb
2026-08-13 19:50:53 +03:30

115 lines
4.1 KiB
Ruby

# encoding: UTF-8
# frozen_string_literal: true
module Workers
module Daemons
class Blockchain < Base
class Runner
attr_reader :ts, :thread
def initialize(blockchain, ts)
@blockchain = blockchain
@ts = ts
@thread = nil
end
def start
@thread ||= Thread.new do
bc_service = BlockchainService.new(@blockchain)
Rails.logger.info { "Processing #{@blockchain.name} blocks." }
loop do
begin
# Reset blockchain_service state.
bc_service.reset!
if @blockchain.reload.height + @blockchain.min_confirmations >= bc_service.latest_block_number
Rails.logger.info { "Skip synchronization. No new blocks detected, height: #{@blockchain.height}, latest_block: #{bc_service.latest_block_number}." }
Rails.logger.info { "Sleeping for 10 seconds" }
sleep(10)
next
end
from_block = @blockchain.height || 0
(from_block..bc_service.latest_block_number).each do |block_id|
Rails.logger.info { "Started processing #{@blockchain.key} block number #{block_id}." }
block_json = bc_service.process_block(block_id)
Rails.logger.info { "Fetch #{block_json.transactions.count} transactions in block number #{block_id}." }
bc_service.update_height(block_id)
Rails.logger.info { "Finished processing #{@blockchain.key} block number #{block_id}." }
end
rescue StandardError => e
report_exception(e)
Rails.logger.warn { "Error: #{e}. Sleeping for 10 seconds" }
sleep(10)
end
end
end
end
def stop
@thread&.kill
end
end
def run
@runner_pool = ::Blockchain.active.each_with_object({}) do |b, pool|
max_ts = [b.currencies.maximum(:updated_at), b.updated_at].compact.max.to_i
logger.warn { "Creating the runner for #{b.key}" }
pool[b.key] = Runner.new(b, max_ts).tap(&:start)
end
while running
begin
# Stop disabled blockchains runners first.
(@runner_pool.keys - ::Blockchain.active.pluck(:key)).each do |b_key|
logger.warn { "Stopping the runner for #{b_key} (blockchain is not active anymore)" }
@runner_pool.delete(b_key).stop
end
# Recreate active blockchain runners by comparing runner &
# maximum blockchain & currencies updated_at timestamp.
::Blockchain.active.each do |b|
max_ts = [b.currencies.maximum(:updated_at), b.updated_at].compact.max.to_i
if @runner_pool[b.key].blank?
logger.warn { "Starting the new runner for #{b.key} (no runner found in pool)" }
@runner_pool[b.key] = Runner.new(b, max_ts).tap(&:start)
elsif @runner_pool[b.key].ts < max_ts
logger.warn { "Recreating a runner for #{b.key} (#{Time.at(@runner_pool[b.key].ts)} < #{Time.at(max_ts)})" }
@runner_pool.delete(b.key).stop
@runner_pool[b.key] = Runner.new(b, max_ts).tap(&:start)
else
logger.warn { "The runner for #{b.key} is up to date (#{Time.at(@runner_pool[b.key].ts)} >= #{Time.at(max_ts)})" }
end
end
logger.info { "Current runners timestamps:" }
logger.info do
@runner_pool.transform_values(&:ts)
end
# Check for blockchain config changes in 30 seconds.
sleep 30
rescue StandardError => e
raise e if is_db_connection_error?(e)
report_exception(e)
Rails.logger.warn { "Error: #{e}. Sleeping for 10 seconds" }
sleep(10)
end
end
end
def stop
@running = false
@runner_pool.each { |_bc_key, runner| runner.stop }
end
end
end
end