Initial commit
This commit is contained in:
159
app/services/blockchain_service.rb
Normal file
159
app/services/blockchain_service.rb
Normal 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
|
||||
Reference in New Issue
Block a user