Initial commit

This commit is contained in:
Yaser
2026-08-13 19:50:53 +03:30
commit 38084458fe
879 changed files with 95198 additions and 0 deletions

View File

@@ -0,0 +1,159 @@
class BlockchainService
Error = Class.new(StandardError)
BalanceLoadError = Class.new(StandardError)
attr_reader :blockchain, :whitelisted_smart_contract, :currencies, :adapter
def initialize(blockchain)
@blockchain = blockchain
@currencies = blockchain.currencies.deposit_enabled
@whitelisted_addresses = blockchain.whitelisted_smart_contracts.active
@adapter = Peatio::Blockchain.registry[blockchain.client.to_sym].new
@adapter.configure(server: @blockchain.server,
currencies: @currencies.map(&:to_blockchain_api_settings),
whitelisted_addresses: @whitelisted_addresses)
end
def latest_block_number
@latest_block_number ||= @adapter.latest_block_number
end
def load_balance!(address, currency_id)
@adapter.load_balance_of_address!(address, currency_id)
rescue Peatio::Blockchain::Error => e
report_exception(e)
raise BalanceLoadError
end
def case_sensitive?
@adapter.features[:case_sensitive]
end
def supports_cash_addr_format?
@adapter.features[:cash_addr_format]
end
def fetch_transaction(transaction)
tx = Peatio::Transaction.new(currency_id: transaction.currency_id,
hash: transaction.txid,
to_address: transaction.rid,
amount: transaction.amount)
if @adapter.respond_to?(:fetch_transaction)
@adapter.fetch_transaction(tx)
else
tx
end
end
def process_block(block_number)
block = @adapter.fetch_block!(block_number)
deposits = filter_deposits(block)
withdrawals = filter_withdrawals(block)
# TODO: Process Transactions with `pending` status
accepted_deposits = []
ActiveRecord::Base.transaction do
accepted_deposits = deposits.map(&method(:update_or_create_deposit)).compact
withdrawals.each(&method(:update_withdrawal))
end
accepted_deposits.each(&:process!)
block
end
# Resets current cached state.
def reset!
@latest_block_number = nil
end
def update_height(block_number)
raise Error, "#{blockchain.name} height was reset." if blockchain.height != blockchain.reload.height
# NOTE: We use update_column to not change updated_at timestamp
# because we use it for detecting blockchain configuration changes see Workers::Daemon::Blockchain#run.
blockchain.update_column(:height, block_number) if latest_block_number - block_number >= blockchain.min_confirmations
end
private
def filter_deposits(block)
addresses = PaymentAddress.where(wallet: Wallet.deposit.with_currency(@currencies.codes), address: block.transactions.map(&:to_address)).pluck(:address)
block.select { |transaction| transaction.to_address.in?(addresses) }
end
def filter_withdrawals(block)
# TODO: Process addresses in batch in case of huge number of confirming withdrawals.
withdraw_txids = Withdraws::Coin.confirming.where(currency: @currencies).pluck(:txid)
block.select { |transaction| transaction.hash.in?(withdraw_txids) }
end
def update_or_create_deposit(transaction)
if transaction.amount < Currency.find(transaction.currency_id).min_deposit_amount
# Currently we just skip tiny deposits.
Rails.logger.info do
"Skipped deposit with txid: #{transaction.hash} with amount: #{transaction.hash}"\
" to #{transaction.to_address} in block number #{transaction.block_number}"
end
return
end
# Fetch transaction from a blockchain that has `pending` status.
transaction = adapter.fetch_transaction(transaction) if @adapter.respond_to?(:fetch_transaction) && transaction.status.pending?
return unless transaction.status.success?
address = PaymentAddress.find_by(wallet: Wallet.deposit_wallet(transaction.currency_id), address: transaction.to_address)
return if address.blank?
# Skip deposit tx if there is tx for deposit collection process
# TODO: select only pending transactions
tx_collect = Transaction.where(txid: transaction.hash, reference_type: 'Deposit')
return if tx_collect.present?
if transaction.from_addresses.blank? && adapter.respond_to?(:transaction_sources)
transaction.from_addresses = adapter.transaction_sources(transaction)
end
deposit =
Deposits::Coin.find_or_create_by!(
currency_id: transaction.currency_id,
txid: transaction.hash,
txout: transaction.txout
) do |d|
d.address = transaction.to_address
d.amount = transaction.amount
d.member = address.member
d.from_addresses = transaction.from_addresses
d.block_number = transaction.block_number
end
deposit.update_column(:block_number, transaction.block_number) if deposit.block_number != transaction.block_number
# Manually calculating deposit confirmations, because blockchain height is not updated yet.
if latest_block_number - deposit.block_number >= @blockchain.min_confirmations && deposit.accept!
deposit
else
nil
end
end
def update_withdrawal(transaction)
withdrawal =
Withdraws::Coin.confirming
.find_by(currency_id: transaction.currency_id, txid: transaction.hash)
# Skip non-existing in database withdrawals.
if withdrawal.blank?
Rails.logger.info { "Skipped withdrawal: #{transaction.hash}." }
return
end
withdrawal.update_column(:block_number, transaction.block_number)
# Fetch transaction from a blockchain that has `pending` status.
transaction = adapter.fetch_transaction(transaction) if @adapter.respond_to?(:fetch_transaction) && transaction.status.pending?
# Manually calculating withdrawal confirmations, because blockchain height is not updated yet.
if transaction.status.failed?
withdrawal.fail!
elsif transaction.status.success? && latest_block_number - withdrawal.block_number >= @blockchain.min_confirmations
withdrawal.success!
end
end
end