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

9
lib/tasks/accounts.rake Normal file
View File

@@ -0,0 +1,9 @@
# encoding: UTF-8
# frozen_string_literal: true
namespace :accounts do
desc 'Create missing accounts for existing members.'
task touch: :environment do
Member.find_each(&:touch_accounts)
end
end

View File

@@ -0,0 +1,67 @@
require 'faraday'
require 'thread/pool'
require 'progress_bar'
require "#{Rails.root}/lib/api_bench.rb"
yml_data = YAML.load_file("#{Rails.root}/config/bench/api_bench.yml")
namespace :api_benchmark do
desc 'create random request for order creation and do trading'
task :random_fire do
bar = ProgressBar.new(yml_data.dig('order_count'), :percentage, :bar, :elapsed, :eta, :rate)
pool = Thread.pool(5)
creator = ApiBench.new
start_time = Time.now
yml_data.dig('order_count').times do |_i|
pool.process do
price = rand(1000..1500)
side = price.odd? ? 'sell' : 'buy'
creator.create_order(yml_data.dig('base_url'),
'btcusd',
side,
rand.round(2),
yml_data.dig('server_token'),
'limit',
price)
bar.increment!
end
end
pool.shutdown
puts 'elapsed_time:'
puts Time.now - start_time
end
desc 'create one big sell request for support a lot of small buy order for trading'
task :direct_fire do
bar = ProgressBar.new(yml_data.dig('order_count'), :percentage, :bar, :elapsed, :eta, :rate)
pool = Thread.pool(5)
creator = ApiBench.new
creator.create_order(yml_data.dig('base_url'),
'btcusd',
'sell',
yml_data.dig('order_count'),
yml_data.dig('server_token'),
'limit',
1000)
# sleep(1)
start_time = Time.now
yml_data.dig('order_count').times do |_i|
pool.process do
price = rand(1001..1500)
creator.create_order(yml_data.dig('base_url'),
'btcusd',
'buy',
1,
yml_data.dig('server_token'),
'limit',
price)
bar.increment!
end
end
pool.shutdown
puts 'elapsed_time:'
puts Time.now - start_time
end
end

View File

@@ -0,0 +1,54 @@
# encoding: UTF-8
# frozen_string_literal: true
# NOTE: only doing this in development as some production environments (Heroku)
# NOTE: are sensitive to local FS writes, and besides -- it's just not proper
# NOTE: to have a dev-mode tool do its thing in production.
if Rails.env.development? && defined?(Annotate)
task :set_annotation_options do
# You can override any of these by setting an environment variable of the
# same name.
Annotate.set_defaults \
'routes' => 'false',
'position_in_routes' => 'before',
'position_in_class' => 'after',
'position_in_test' => 'before',
'position_in_fixture' => 'before',
'position_in_factory' => 'before',
'position_in_serializer' => 'before',
'show_foreign_keys' => 'true',
'show_complete_foreign_keys' => 'true',
'show_indexes' => 'true',
'simple_indexes' => 'false',
'model_dir' => 'app/models',
'root_dir' => '',
'include_version' => 'true',
'require' => '',
'exclude_tests' => 'true',
'exclude_fixtures' => 'true',
'exclude_factories' => 'true',
'exclude_serializers' => 'true',
'exclude_scaffolds' => 'true',
'exclude_controllers' => 'true',
'exclude_helpers' => 'true',
'exclude_sti_subclasses' => 'false',
'ignore_model_sub_dir' => 'false',
'ignore_columns' => nil,
'ignore_routes' => nil,
'ignore_unknown_models' => 'false',
'hide_limit_column_types' => '',
'hide_default_column_types' => '',
'skip_on_db_migrate' => 'false',
'format_bare' => 'true',
'format_rdoc' => 'false',
'format_markdown' => 'false',
'sort' => 'false',
'force' => 'false',
'trace' => 'false',
'wrapper_open' => nil,
'wrapper_close' => nil,
'with_comment' => true
end
Annotate.load_tasks
end

32
lib/tasks/barong.rake Normal file
View File

@@ -0,0 +1,32 @@
# encoding: UTF-8
# frozen_string_literal: true
namespace :barong do
desc 'Refresh access level for Barong members.'
task levels: :environment do
url = "https://#{ENV.fetch('BARONG_DOMAIN')}/api/account"
t = Authentication.arel_table
Authentication
.where(provider: :barong)
.where(t[:token].is_not_blank)
.where(t[:member_id].is_not_blank)
.includes(:member)
.order(updated_at: :asc)
.limit(1000)
.each do |auth|
next if auth.token.blank? || auth.member.blank?
profile = JSON.parse(Faraday.get(url, nil, 'Authorization' => "Bearer #{auth.token}").assert_success!.body)
current_level = auth.member.level
new_level = profile.fetch('level')
unless current_level == new_level
auth.member.update!(level: new_level)
auth.touch
Rails.logger.info { "#{auth.member.email}: #{current_level} >> #{new_level}." }
end
rescue => e
report_exception(e)
end
end
end

140
lib/tasks/bench.rake Normal file
View File

@@ -0,0 +1,140 @@
# frozen_string_literal: true
# TODO: Add descriptions.
# TODO: Don't sleep in case of last bench.
# TODO: Remove legacy benchmarks.
namespace :bench do
namespace :matching do
desc 'Matching with amqp messages'
task :amqp, [:config_load_path] => [:environment] do |_t, args|
args.with_defaults(:config_load_path => 'config/bench/matching.yml')
benches =
YAML.load_file(Rails.root.join(args[:config_load_path]))
.map(&:deep_symbolize_keys)
.each_with_object([]) do |config, memo|
Kernel.pp config
matching = Bench::Matching::AMQP.new(config)
matching.run!
memo << matching
matching.save_report
Kernel.puts "Sleep before next bench"
sleep 5
end
benches.each {|b| Kernel.pp b.result}
end
desc 'Matching without amqp messages'
task :direct, [:config_load_path] => [:environment] do |_t, args|
args.with_defaults(:config_load_path => 'config/bench/matching.yml')
benches =
YAML.load_file(Rails.root.join(args[:config_load_path]))
.map(&:deep_symbolize_keys)
.each_with_object([]) do |config, memo|
Kernel.pp config
matching = Bench::Matching::Direct.new(config)
matching.run!
memo << matching
matching.save_report
Kernel.puts "Sleep before next bench"
sleep 5
end
benches.each {|b| Kernel.pp b.result}
end
end
namespace :trade_execution do
desc 'Trade Execution with amqp messages'
task :amqp, [:config_load_path] => [:environment] do |_t, args|
args.with_defaults(:config_load_path => 'config/bench/trade_execution.yml')
benches =
YAML.load_file(Rails.root.join(args[:config_load_path]))
.map(&:deep_symbolize_keys)
.each_with_object([]) do |config, memo|
Kernel.pp config
trade_execution = ::Bench::TradeExecution::AMQP.new(config)
trade_execution.run!
memo << trade_execution
trade_execution.save_report
Kernel.puts "Sleep before next bench"
sleep 5
end
benches.each {|b| Kernel.pp b.result}
end
desc 'Trade execution without amqp messages'
task :direct, [:config_load_path] => [:environment] do |_t, args|
args.with_defaults(:config_load_path => 'config/bench/trade_execution.yml')
benches =
YAML.load_file(Rails.root.join(args[:config_load_path]))
.map(&:deep_symbolize_keys)
.each_with_object([]) do |config, memo|
Kernel.pp config
trade_execution = ::Bench::TradeExecution::Direct.new(config)
trade_execution.run!
memo << trade_execution
trade_execution.save_report
Kernel.puts "Sleep before next bench"
sleep 5
end
benches.each {|b| Kernel.pp b.result}
end
end
namespace :order_processing do
desc 'Order Processing with amqp messages'
task :amqp, [:config_load_path] => [:environment] do |_t, args|
args.with_defaults(:config_load_path => 'config/bench/order_processing.yml')
benches =
YAML.load_file(Rails.root.join(args[:config_load_path]))
.map(&:deep_symbolize_keys)
.each_with_object([]) do |config, memo|
Kernel.pp config
order_processing = ::Bench::OrderProcessing::AMQP.new(config)
order_processing.run!
memo << order_processing
order_processing.save_report
Kernel.puts "Sleep before next bench"
sleep 5
end
benches.each {|b| Kernel.pp b.result}
end
desc 'Order Processing without amqp messages'
task :direct, [:config_load_path] => [:environment] do |_t, args|
args.with_defaults(:config_load_path => 'config/bench/order_processing.yml')
benches =
YAML.load_file(Rails.root.join(args[:config_load_path]))
.map(&:deep_symbolize_keys)
.each_with_object([]) do |config, memo|
Kernel.pp config
order_processing = Bench::OrderProcessing::Direct.new(config)
order_processing.run!
memo << order_processing
order_processing.save_report
Kernel.puts "Sleep before next bench"
sleep 5
end
benches.each { |b| Kernel.pp b.result }
end
end
end

72
lib/tasks/benchmark.rake Normal file
View File

@@ -0,0 +1,72 @@
namespace :benchmark do
desc "In memory matching engine benchmark"
task :matching => %w(environment) do
max_round = round(2)
puts "\n>> Setup environment (num=#{num} round=#{max_round})"
Dir[Rails.root.join('tmp', 'matching_result_*')].each {|f| FileUtils.rm(f) }
Benchmark::Matching.new(label, num, max_round).run
end
desc "Trade execution benchmark"
task :execution => %w(environment) do
max_round = round(2)
puts "\n>> Setup environment (executor=#{executor} num=#{num} round=#{max_round})"
Dir[Rails.root.join('tmp', 'matching_result_*')].each {|f| FileUtils.rm(f) }
Benchmark::Execution.new(label, num, max_round, executor).run
end
desc "Run integration benchmark"
task :integration => %w(environment) do
puts "Integration Benchmark (num: #{num(400)})\n"
Benchmark::Integration.new(num).run
end
desc "Profiling"
task :profiling, [:type]=> %w(environment) do |task, args|
case args[:type]
when 'matching'
puts "\n>> Setup environment (num=#{num} round=#{round})"
Dir[Rails.root.join('tmp', 'profiling_matching_result_*')].each {|f| FileUtils.rm(f) }
file_path = Rails.root.join('tmp', "profiling_matching_result_#{Time.now.to_i}")
File.open(file_path, 'w') { |file| file.puts "\n>> Setup environment (num=#{num} round=#{round})" }
Benchmark::Profiling.matching(label, num, round, file_path)
when 'execution'
puts "\n>> Setup environment (executor=#{executor} num=#{num} round=#{round})"
Dir[Rails.root.join('tmp', 'profiling_execution_result_*')].each {|f| FileUtils.rm(f) }
file_path = Rails.root.join('tmp', "profiling_execution_result_#{Time.now.to_i}")
File.open(file_path, 'w') { |file| file.puts "\n>> Setup environment (executor=#{executor} num=#{num} round=#{round})" }
Benchmark::Profiling.execution(label, num, round, executor, file_path)
else
puts "\n>> Wrong parameter!"
end
end
def num
ENV['NUM'] ? ENV['NUM'].to_i : 100
end
def round r = 1
ENV['ROUND'] ? ENV['ROUND'].to_i : r
end
def label
ENV['LABEL'] || Time.now.to_i
end
def executor
ENV['EXECUTOR'] ? ENV['EXECUTOR'].to_i : 6
end
end

View File

@@ -0,0 +1,12 @@
namespace :billionaire do
desc 'make your memberes billionaires'
task :by_email, %i[market members] => [:environment] do |_t, args|
include ::Bench::Helpers
memberes_array = args[:members].split(' ')
memberes = ::Member.where(email: memberes_array)
raise 'members if empty' if memberes.empty? || args[:members].blank?
@currencies = ::Currency.where(id: args[:market].split('-').map(&:squish).reject(&:blank?))
memberes.map(&method(:become_billionaire))
end
end

View File

@@ -0,0 +1,11 @@
# encoding: UTF-8
# frozen_string_literal: true
namespace :bitgo do
desc 'Add a webhook that will result in an HTTP callback at the specified URL from BitGo when events are triggered.'
task :webhooks, [:url] => [:environment] do
Wallet.deposit.active.where(gateway: :bitgo).each do |w|
w.service.register_webhooks!(args[:url])
end
end
end

11
lib/tasks/clear.rake Normal file
View File

@@ -0,0 +1,11 @@
# encoding: UTF-8
# frozen_string_literal: true
namespace :clear do
desc 'Clear database from accounting information.'
task accounting: :environment do
table_names = %w[accounts adjustments assets beneficiaries deposits expenses
liabilities orders payment_addresses revenues trades transfers triggers withdraws]
table_names.each { |name| ActiveRecord::Base.connection.execute("TRUNCATE TABLE #{name}") }
end
end

View File

@@ -0,0 +1,42 @@
namespace :bench do
namespace :barong_session do
desc 'create user and create barong session'
task :create => [:environment] do
members = Bench::Barong_session::Sessions.new(trader: 10)
members.run!
end
task :session_create => [:environment] do
config = YAML.load_file("#{Rails.root}/config/application.yml")
session_file = "#{Rails.root}/public/sessions.csv"
file = "#{Rails.root}/public/user_data.csv"
result = []
CSV.foreach(file) do |row|
Kernel.pp row[0]
url = "#{config["development"]["LOG_IN_URL"]}"
url_balance = "#{config["development"]["BALANCE_URL"]}"
resp = Faraday.post(url) do |req|
req.params['email'] = row[0]
req.params['password'] = "#{config["development"]["SIGN_UP_PASSWORD"]}"
Kernel.puts "#{req.params["email"]} tried to log in"
end
balance = Faraday.get(url_balance) do |req|
req.headers['Cookie'] = resp.headers["set-cookie"].split(';')[0]
end
Kernel.pp balance.body
result << resp.headers["set-cookie"].split(';')[0]
end
CSV.open(session_file, 'w+', write_headers: true) do |csv|
result.each do |res|
csv << [res]
end
end
end
end
end

View File

@@ -0,0 +1,42 @@
# frozen_string_literal: true
require 'csv'
namespace :distribution do
# Detailed instruction https://github.com/rubykube/peatio/blob/master/docs/tasks/distribution.md
# Required fields for distribution:
# - uid
# - currency_id
# - amount
#
# Usage:
# For distribution process: -> bundle exec rake distribution:process['file_name.csv']
desc 'Distribution process'
task :process, [:config_load_path] => [:environment] do |_, args|
csv_table = File.read(Rails.root.join(args[:config_load_path]))
count = 0
errors_count = 0
CSV.parse(csv_table, headers: true, quote_empty: false).each do |row|
row = row.to_h.compact.symbolize_keys!
uid = row[:uid]
currency_id = row[:currency_id]
amount = row[:amount].to_d
member = Member.find_by_uid!(uid)
currency = Currency.find(currency_id)
account = member.get_account(currency)
ActiveRecord::Base.transaction do
Operations::Asset.credit!(currency: currency, amount: amount, reference_type: 'Distribution')
Operations::Liability.credit!(kind: :main, currency: currency, member_id: member.id, amount: amount, reference_type: 'Distribution')
account.update!(balance: account.balance + amount)
end
rescue StandardError => e
message = { error: e.message, uid: row[:uid] }
::Rails.logger.error message
errors_count += 1
end
Kernel.puts "Distributions processed #{count}"
Kernel.puts "Errored #{errors_count}"
end
end

138
lib/tasks/export.rake Normal file
View File

@@ -0,0 +1,138 @@
# frozen_string_literal: true
require 'yaml'
require 'csv'
require 'peatio/export'
namespace :export do
desc 'Export all configs to yaml files.'
task configs: :environment do
Rake::Task['export:blockchains'].invoke
Rake::Task['export:currencies'].invoke
Rake::Task['export:markets'].invoke
Rake::Task['export:wallets'].invoke
Rake::Task['export:engines'].invoke
Rake::Task['export:trading_fees'].invoke
end
desc 'Export blockchains config to yaml file.'
task :blockchains, [:export_path] => [:environment] do |_, args|
args.with_defaults(export_path: 'config/seed/blockchains.yml')
File.write(args.export_path, Peatio::Export.new.export_blockchains.to_yaml)
end
desc 'Export currencies config to yaml file.'
task :currencies, [:export_path] => [:environment] do |_, args|
args.with_defaults(export_path: 'config/seed/currencies_backup.yml')
File.write(args.export_path, Peatio::Export.new.export_currencies.to_yaml)
end
desc 'Export markets config to yaml file.'
task :markets, [:export_path] => [:environment] do |_, args|
args.with_defaults(export_path: 'config/seed/markets_backup.yml')
File.write(args.export_path, Peatio::Export.new.export_markets.to_yaml)
end
desc 'Export wallets config to yaml file.'
task :wallets, [:export_path] => [:environment] do |_, args|
args.with_defaults(export_path: 'config/seed/wallets_backup.yml')
File.write(args.export_path, Peatio::Export.new.export_wallets.to_yaml)
end
desc 'Export trading fees config to yaml file.'
task :trading_fees, [:export_path] => [:environment] do |_, args|
args.with_defaults(export_path: 'config/seed/trading_fees_backup.yml')
File.write(args.export_path, Peatio::Export.new.export_trading_fees.to_yaml)
end
desc 'Export engines to yaml file.'
task :engines, [:export_path] => [:environment] do |_, args|
args.with_defaults(export_path: 'config/seed/engines_backup.yml')
File.write(args.export_path, Peatio::Export.new.export_engines.to_yaml)
end
desc 'Export all members to csv file.'
task :users, [:export_path] => [:environment] do |_, args|
args.with_defaults(export_path: 'exported_users.csv')
count = 0
errors_count = 0
begin
CSV.open(args.export_path, 'w') do |csv|
csv << %w[uid email level role state]
Member.find_each do |member|
csv << [member.uid, member.email, member.level, member.role, member.state]
count += 1
end
rescue StandardError => e
message = { error: e.message, email: member.email, uid: member.uid }
::Rails.logger.error message
errors_count += 1
end
end
Kernel.puts "Exported #{count} members"
Kernel.puts "Errored #{errors_count}"
end
desc 'Export accounts to csv file.'
task :accounts, [:export_path] => [:environment] do |_, args|
args.with_defaults(export_path: 'exported_accounts.csv')
count = 0
errors_count = 0
begin
CSV.open(args.export_path, 'w') do |csv|
csv << %w[uid currency_id main_balance locked_balance]
Account.find_each do |account|
if account.balance.positive? || account.locked.positive?
csv << [account.member.uid, account.currency_id, account.balance, account.locked]
count += 1
end
end
rescue StandardError => e
message = { error: e.message, uid: account.member.uid, currency_id: account.currency_id }
::Rails.logger.error message
errors_count += 1
end
end
Kernel.puts "Exported #{count} accounts"
Kernel.puts "Errored #{errors_count}"
end
desc 'Export addresses to csv file.'
task :addresses, [:export_path] => [:environment] do |_, args|
args.with_defaults(export_path: 'exported_addresses.csv')
count = 0
errors_count = 0
begin
CSV.open(args.export_path, 'w') do |csv|
csv << %w[uid wallet_name address secret details]
PaymentAddress.find_each do |address|
wallet = Wallet.find(address.wallet_id)
# We save wallet name instead of id because id can change after export/import migration
csv << [address.member.uid, wallet.name, address.address, address.secret, address.details]
count += 1
rescue StandardError => e
message = { error: e.message, uid: address.member.uid, wallet_name: address.wallet.name }
::Rails.logger.error message
errors_count += 1
end
end
end
Kernel.puts "Exported #{count} addresses"
Kernel.puts "Errored #{errors_count}"
end
desc 'Export configs(blockchains, currencies, wallets, markets, engines) from the database'
task :configs, [:export_path] => [:environment] do |_, args|
args.with_defaults(export_path: 'export_configs.yaml')
ex = Peatio::Export.new
File.write(args.export_path, {
'accounts' => ex.export_accounts,
'blockchains' => ex.export_blockchains,
'currencies' => ex.export_currencies,
'markets' => ex.export_markets,
'wallets' => ex.export_wallets,
'trading_fees' => ex.export_trading_fees,
'engines' => ex.export_engines
}.to_yaml)
end
end

37
lib/tasks/failures.rake Normal file
View File

@@ -0,0 +1,37 @@
# encoding: UTF-8
# frozen_string_literal: true
namespace :failures do
desc 'Fetch Trade Execution Errors'
task trade_errors: :environment do
conn = Bunny.new AMQP::Config.connect
conn.start
ch = conn.create_channel
q = ch.queue("peatio.trades.errors")
puts "****** Fetching Queue Messages ******"
count = q.message_count
puts "****** Total Messages in queue = #{count} ******"
errors = []
until count == 0
_delivery_info, _metadata, payload = q.pop
payload = JSON.parse(payload).symbolize_keys!
err_obj = payload.slice(:code, :message)
if err = errors.find{|k| k[:code] == err_obj.fetch(:code)}
err[:count] += 1
else
errors << {code: err_obj.fetch(:code), count: 1 }
end
count -= 1
end
errors.each do |error|
puts "****** Error Code: #{error[:code]} Count = #{error[:count]} ******"
end
puts "****** purging queue *******"
q.purge
end
end

26
lib/tasks/fetch.rake Normal file
View File

@@ -0,0 +1,26 @@
# frozen_string_literal: true
namespace :fetch do
desc 'Fetch currency price'
task :price, %i[quote external_currencies_service] => [:environment] do |_, args|
# TODO
# Add ability to take price from platform market
Currency.find_in_batches(batch_size: 50) do |group|
group.each do |record|
url = args.external_currencies_service || ENV['EXTERNAL_CURRENCIES_SERVICE']
raise 'There is no external currencies service configured' unless url.present?
# API call to external currencies service to get current currency price
response = Faraday.get(url, { code: record.code, quote: args.quote })
response_body = JSON.parse(response.body)
next unless response_body['current_price'].present?
::Rails.logger.info { "Updating currency #{record.code} with price #{response_body["current_price"]}" }
record.update!(price: response_body['current_price'])
rescue Faraday::Error, StandardError => e
::Rails.logger.error e.inspect
end
end
end
end

196
lib/tasks/import.rake Normal file
View File

@@ -0,0 +1,196 @@
# frozen_string_literal: true
require 'csv'
require 'peatio/import'
namespace :import do
# Detailed instruction https://github.com/rubykube/peatio/blob/master/docs/tasks/import.md
# Required fields for import users:
# - uid
# - email
#
# Usage:
# For import users: -> bundle exec rake import:users['file_name.csv']
desc 'Load members from csv file.'
task :users, [:config_load_path] => [:environment] do |_, args|
csv_table = File.read(Rails.root.join(args[:config_load_path]))
count = 0
errors_count = 0
CSV.parse(csv_table, headers: true, quote_empty: false).each do |row|
row = row.to_h.compact.symbolize_keys!
defaults = { level: 0, role: 'member', state: 'active' }
permitted_attr = %i[uid email level role state]
Member.create!(row.slice(*permitted_attr).reverse_merge(defaults))
count += 1
rescue StandardError => e
message = { error: e.message, email: row[:email], uid: row[:uid] }
Rails.logger.error message
errors_count += 1
end
Kernel.puts "Created #{count} members"
Kernel.puts "Errored #{errors_count}"
end
# Required fields for import accounts balances:
# - uid
# - currency_id
#
# Make sure that you create required currency
# Usage:
# For import account balances: -> bundle exec rake import:accounts['file_name.csv']
desc 'Load accounts balances from csv file.'
task :accounts, %i[config_load_path balance_check] => [:environment] do |_, args|
args.with_defaults(:config_load_path => 'exported_accounts.csv', :balance_check => false)
csv_table = File.read(Rails.root.join(args[:config_load_path]))
count = 0
errors_count = 0
CSV.parse(csv_table, headers: true).each do |row|
row = row.to_h.compact.symbolize_keys!
uid = row[:uid]
member = Member.find_by_uid!(uid)
currency = Currency.find(row[:currency_id])
account = Account.find_or_create_by!(member: member, currency: currency)
main_balance = row[:main_balance].to_d
locked_balance = row[:locked_balance].to_d
next if args[:balance_check] == 'true' && main_balance <= 0 && locked_balance <= 0
ActiveRecord::Base.transaction do
Operations::Asset.credit!(currency: currency, amount: main_balance + locked_balance)
Operations::Liability.credit!(kind: :main, currency: currency, member_id: member.id, amount: main_balance)
Operations::Liability.credit!(kind: :locked, currency: currency, member_id: member.id, amount: locked_balance)
account.update!(balance: main_balance, locked: locked_balance)
count += 1
end
rescue StandardError => e
message = { error: e.message, uid: row[:uid] }
Rails.logger.error message
errors_count += 1
end
Kernel.puts "Accounts created #{count}"
Kernel.puts "Errored #{errors_count}"
end
desc 'Load addresses from csv file. Export file from Peatio version >= 2.6.0'
task :addresses, [:config_load_path] => [:environment] do |_, args|
args.with_defaults(:config_load_path => 'exported_addresses.csv')
csv_table = File.read(Rails.root.join(args[:config_load_path]))
count = 0
errors_count = 0
CSV.parse(csv_table, headers: true).each do |row|
row = row.to_h.compact.symbolize_keys!
uid = row[:uid]
member = Member.find_by_uid!(uid)
wallet = Wallet.find_by(name: row[:wallet_name])
PaymentAddress.create(member_id: member.id, wallet_id: wallet.id, address: row[:address], secret: row[:secret], details: row[:details])
count += 1
rescue StandardError => e
message = { error: e.message, uid: row[:uid], currency_id: currency_id[:currency_id] }
Rails.logger.error message
errors_count += 1
end
Kernel.puts "Addresses created #{count}"
Kernel.puts "Errored #{errors_count}"
end
desc 'Load addresses from csv file. Export file from Peatio version < 2.6.0'
task :addresses_legacy, [:config_load_path] => [:environment] do |_, args|
args.with_defaults(:config_load_path => 'exported_addresses.csv')
csv_table = File.read(Rails.root.join(args[:config_load_path]))
count = 0
errors_count = 0
CSV.parse(csv_table, headers: true).each do |row|
row = row.to_h.compact.symbolize_keys!
uid = row[:uid]
member = Member.find_by_uid!(uid)
wallet = Wallet.deposit_wallet(row[:currency_id])
PaymentAddress.create(member_id: member.id, wallet_id: wallet.id, address: row[:address], secret: row[:secret], details: row[:details])
count += 1
rescue StandardError => e
message = { error: e.message, uid: row[:uid], currency_id: currency_id[:currency_id] }
Rails.logger.error message
errors_count += 1
end
Kernel.puts "Addresses created #{count}"
Kernel.puts "Errored #{errors_count}"
end
desc 'Load whitelisted smart contracts from CSV'
task :whitelisted_smart_contracts, [:config_load_path] => [:environment] do |_, args|
args.with_defaults(:config_load_path => 'exported_whitelisted_smart_contracts.csv')
csv_table = File.read(Rails.root.join(args[:config_load_path]))
count = 0
errors_count = 0
CSV.parse(csv_table, headers: true, quote_empty: false).each do |row|
row = row.to_h.compact.symbolize_keys!
address = row[:address]
blockchain_key = row[:blockchain_key]
description = row[:description]
next if address.blank? || blockchain_key.blank? || ::Blockchain.pluck(:key).exclude?(blockchain_key)
::WhitelistedSmartContract.create!(description: description, address: address,
blockchain_key: blockchain_key, state: 'active')
count += 1
rescue StandardError => e
message = { error: e.message, uid: row[:uid], currency_id: currency_id[:currency_id] }
Rails.logger.error message
errors_count += 1
end
Kernel.puts "whitelisted contracts created #{count}"
Kernel.puts "Errored #{errors_count}"
end
desc 'Import configs(accounts, blockchains, currencies, wallets, trading_fees, markets, engines, whitelisted_smart_contracts) to the database'
task :configs, [:config_load_path] => :environment do |_, args|
args.with_defaults(config_load_path: 'import_configs.yaml')
import_data = YAML.load_file(Rails.root.join(args[:config_load_path]))
Peatio::Import.new(import_data).load_all
end
desc 'Load local trades to the Influx. By default, it will load trades starting from the last id in Influx'
task :trade_to_influx, [:full_load] => :environment do |_, args|
args.with_defaults(full_load: 'false')
if args.full_load == 'false'
ids = []
Peatio::InfluxDB.config[:host].each do |host|
client = Peatio::InfluxDB.client(host: [host])
client.query('SELECT id from trades ORDER BY desc limit 1') do |_name, _tags, points|
ids << points.map(&:deep_symbolize_keys!).first[:id]
end
end
last_id = ids.max
Trade.where('id > ?', last_id.to_i).find_in_batches do |batch|
process_trades_batch(batch)
end
elsif args.full_load == 'true'
Trade.find_in_batches do |batch|
process_trades_batch(batch)
end
end
end
def process_trades_batch(batch)
batch.each_with_index do |trade, index|
# We will convert created_at to ms and update it with index to make sure that we have unique
# timestamps for each trade because influxdb use timestamp as unique identifier.
influx_data = trade.influx_data.merge(timestamp: trade.created_at.to_i * 1000 + index)
Peatio::InfluxDB.client(keyshard: trade.market_id).write_point('trades', influx_data, "ms")
end
end
desc 'Build candles for all trades in influx'
task influx_build_candles: :environment do
prev_from = 'trades'
Peatio::InfluxDB.config[:host].each do |host|
client = Peatio::InfluxDB.client(host: [host])
client.query('SELECT FIRST(price) AS open, max(price) AS high, min(price) AS low, last(price) AS close, sum(amount) AS volume INTO candles_1m FROM trades GROUP BY time(1m), market')
prev_from = 'candles_1m'
KLineService::HUMANIZED_POINT_PERIODS.except(1).each do |_, v|
client.query("SELECT FIRST(open) as open, MAX(high) as high, MIN(low) as low, LAST(close) as close, SUM(volume) as volume INTO candles_#{v} FROM #{prev_from} GROUP BY time(#{v}), market")
prev_from = "candles_#{v}"
end
end
end
end

61
lib/tasks/init_order.rake Normal file
View File

@@ -0,0 +1,61 @@
namespace :init_order do
desc 'initalizing orders for testing'
task :fire, %i[order_count member_count] => [:environment] do |_t, args|
include ::Bench::Helpers
raise 'please set config' if args[:order_count].blank? || args[:member_count].blank?
my_config = { injector: 'dummy', number: args[:order_count].to_i, step: 100, markets: 'btcusd',
currencies: 'btc,usd', min_volume: 0.001, max_volume: 0.005 }
memberes = ::Bench::Factories.create_list(:member, args[:member_count].to_i)
@currencies = ::Currency.where(id: my_config[:currencies].split(',').map(&:squish).reject(&:blank?))
raise 'check currencies' unless @currencies.present?
raise 'check members' unless memberes.present?
memberes.map(&method(:become_billionaire))
buy_config = my_config.merge({ min_price: 19_000, max_price: 20_000 })
buy_injector = ::Bench::Injectors.initialize_injector(buy_config)
(1..my_config[:number]).each do |_i|
my_create_order(buy_injector.construct_order(memberes, 'OrderBid'))
end
sell_config = my_config.merge({ min_price: 50_000, max_price: 51_000 })
sell_injector = ::Bench::Injectors.initialize_injector(sell_config)
(1..my_config[:number]).each do |_i|
my_create_order(sell_injector.construct_order(memberes, 'OrderAsk'))
end
end
task :fire_token => [:environment] do
raise 'secretFile is not existed' unless File.exist?("#{Rails.root}/config/secrets/rsa-key")
secret_file = File.read("#{Rails.root}/config/secrets/rsa-key")
puts secret_file
raise 'secretFile is not existed' unless File.exist?("#{Rails.root}/config/secrets/rsa-key.pub")
public_file = File.read("#{Rails.root}/config/secrets/rsa-key.pub")
puts public_file
token_file = "#{Rails.root}/public/member_jwt_token.csv"
JWT_ALGORITHM = 'RS256'
ENCODED_PRIVATE_KEY = Base64.urlsafe_encode64(secret_file)
ENCODED_PUBLIC_KEY = Base64.urlsafe_encode64(public_file)
result = []
Member.all.each do |member|
payload = { uid: member.uid, email: member.email, level: member.level,
role: member.role, state: "active", aud: "peatio", sub: "session",
jti: SecureRandom.uuid }
token = JWT.encode(payload, OpenSSL::PKey.read(Base64.urlsafe_decode64(ENCODED_PRIVATE_KEY)), JWT_ALGORITHM)
puts [member.uid, member.email, token]
result << [member.uid.to_s, member.email.to_s, token.to_s]
end
CSV.open(token_file, 'w', write_headers: true) do |csv|
result.each do |res|
csv << res
end
puts 'FINISHED'
end
end
end

93
lib/tasks/job.rake Normal file
View File

@@ -0,0 +1,93 @@
# frozen_string_literal: true
namespace :job do
namespace :order do
desc 'Close orders older than ORDER_MAX_AGE.'
task close: :environment do
Job.execute('close_orders') do
order_max_age = ENV.fetch('ORDER_MAX_AGE', 2_419_200).to_i
# Cancel orders that older than max_order_age
orders = Order.where('created_at < ? AND state = ?', Time.now - order_max_age, 100)
orders.each do |o|
Order.cancel(o.id)
end
{ pointer: Time.now.to_s(:db), counter: orders.count }
end
end
desc 'Archive and delete old cancelled orders without trades to the archive database.'
task archive: :environment do
Job.execute('archive_orders') do
time = Time.now
# default batch 1000
count = Order.where(state: :cancel, trades_count: 0).where('updated_at < ?', time - 1.week).count
Order.where(state: :cancel, trades_count: 0).where('updated_at < ?', time - 1.week).find_in_batches do |batch|
ActiveRecord::Base.establish_connection(:archive_db)
batch.each do |o|
Order.new(o.attributes).save!(validate: false)
end
ActiveRecord::Base.establish_connection
batch.each do |o|
o.delete
end
end
{ pointer: time, counter: count }
end
end
def order_insert
'INSERT INTO orders (id, uuid, remote_id, bid, ask, market_id, price, ' \
'volume, origin_volume, maker_fee, taker_fee, state, type, member_id, ord_type, ' \
'locked, origin_locked, funds_received, trades_count, created_at, updated_at) VALUES ' \
end
def order_values(order)
order['remote_id'] = order['remote_id'].nil? ? 'NULL' : order['remote_id']
order['uuid'] = UUID::Type.new.quoted_id(order['uuid'])
"(#{order['id']}, #{order['uuid']}, #{order['remote_id']}, " \
"'#{order['bid']}', '#{order['ask']}', '#{order['market_id']}', " \
"#{order['price']}, #{order['volume']}, #{order['origin_volume']}, " \
"#{order['maker_fee']}, #{order['taker_fee']}, #{order['state']}, " \
"'#{order['type']}', #{order['member_id']}, '#{order['ord_type']}', " \
"#{order['locked']}, #{order['origin_locked']}, #{order['funds_received']}, " \
"#{order['trades_count']}, '#{order['created_at']}', '#{order['updated_at']}')"
end
end
namespace :liabilities do
desc 'Compact liabilities using Stored Procedure'
task :compact_orders, %i[min_time max_time] => [:environment] do |_, args|
Job.execute('compact_orders') do
# Connection to the main database
main_db = if Rails.configuration.database_adapter.downcase == 'PostgreSQL'.downcase
ActiveRecord::Base.connection.raw_connection
else
Mysql2::Client.new(sql_config(ENV.fetch('RAILS_ENV', 'development')))
end
# Execute Stored Procedure for Liabilities compacting
# Example:
# Current date: "2020-07-30 16:39:15"
# min_time: "2020-07-23 00:00:00"
# max_time: "2020-07-24 00:00:00"
# Compact liabilities beetwen: "2020-07-23 00:00:00" and "2020-07-24 00:00:00"
args.with_defaults(min_time: (Time.now - 1.week).beginning_of_day.to_s(:db),
max_time: (Time.now - 6.day).beginning_of_day.to_s(:db))
result = if Rails.configuration.database_adapter.downcase == 'PostgreSQL'.downcase
main_db.query("select * from compact_orders('#{args.min_time}'::date, '#{args.max_time}'::date);")
else
main_db.query("call compact_orders('#{args.min_time}', '#{args.max_time}');")
end
result.first
end
end
end
def sql_config(namespace)
yaml = ::Pathname.new('config/database.yml')
return {} unless yaml.exist?
::SafeYAML.load(::ERB.new(yaml.read).result)[namespace]
end
end

37
lib/tasks/release.rake Normal file
View File

@@ -0,0 +1,37 @@
require 'bump'
def bot_username
ENV.fetch('BOT_USERNAME', 'kite-bot')
end
def repository_slug
ENV.fetch('REPOSITORY_SLUG', 'openware/peatio')
end
namespace 'release' do
desc 'Bump the version of the application'
task :travis do
unless ENV['TRAVIS_BRANCH'] == 'master' || ENV['TRAVIS_BRANCH'].match?(/^[0-9]+-[0-9]+-stable$/)
Kernel.abort 'Bumping version aborted: GitHub pull request detected.'
end
if ENV['TRAVIS_PULL_REQUEST'] != 'false'
Kernel.abort 'Bumping version aborted: GitHub pull request detected.'
end
unless ENV['TRAVIS_TAG'].to_s.empty?
Kernel.abort 'Bumping version aborted: the build has been triggered by Git tag.'
end
sh %(git config --global user.name 'OpenWare')
sh %(git config --global user.email 'support@openware.com')
sh %(git remote add authenticated-origin https://#{bot_username}:#{ENV.fetch('GITHUB_API_KEY')}@github.com/#{repository_slug})
next_version = Bump::Bump.next_version('patch')
sh %(V='#{next_version}' bin/gendocs)
sh %(git add -A)
Bump::Bump.run('patch', commit_message: '[skip ci]', tag: false)
sh %(git tag #{Bump::Bump.current})
sh %(git push --tags authenticated-origin HEAD:#{ENV.fetch('TRAVIS_BRANCH')})
end
end

17
lib/tasks/revert.rake Normal file
View File

@@ -0,0 +1,17 @@
# encoding: UTF-8
# frozen_string_literal: true
# Usage
# for revert all trading activity for particular user:
# $> bundle exec rake revert:trading_activity["admin@barong.io"]
namespace :revert do
desc 'Revert user trade activity.'
task :trading_activity, [:member_email] => [:environment] do |_, args|
member = Member.find_by(email: args[:member_email])
# For each trade create revert Liabilities, Revenues and update User balances
# TODO: Add ability to revert particular trades.
member.revert_trading_activity!(member.trades.order(id: :desc))
end
end

143
lib/tasks/seed.rake Normal file
View File

@@ -0,0 +1,143 @@
# encoding: UTF-8
# frozen_string_literal: true
require 'yaml'
namespace :seed do
desc 'Adds missing accounts to database defined at config/seed/accounts.yml.'
task accounts: :environment do
Operations::Account.transaction do
YAML.load_file(Rails.root.join('config/seed/accounts.yml')).each do |hash|
next if Operations::Account.exists?(code: hash.fetch('code'))
Operations::Account.create!(hash)
end
end
end
# TODO: Deprecate seed tasks in favour of import:configs
desc 'Adds missing currencies to database defined at config/seed/currencies.yml.'
task currencies: :environment do
Currency.transaction do
YAML.load_file(Rails.root.join('config/seed/currencies.yml')).each do |hash|
next if Currency.exists?(id: hash.fetch('id'))
Currency.create!(hash)
end
end
end
desc 'Adds missing blockchains to database defined at config/seed/blockchains.yml.'
task blockchains: :environment do
Blockchain.transaction do
YAML.load_file(Rails.root.join('config/seed/blockchains.yml')).each do |hash|
next if Blockchain.exists?(key: hash.fetch('key'))
Blockchain.create!(hash)
end
end
end
desc 'Adds missing engines to database defined at config/seed/engines.yml.'
task engines: :environment do
Engine.transaction do
YAML.load_file(Rails.root.join('config/seed/engines.yml')).each do |hash|
next if Engine.exists?(name: hash.fetch('name'))
Engine.create!(hash)
end
end
end
desc 'Adds missing markets to database defined at config/seed/markets.yml.'
task markets: :environment do
Market.transaction do
YAML.load_file(Rails.root.join('config/seed/markets.yml'))
.map(&:symbolize_keys)
.each do |hash|
next if Market.exists?(id: hash.fetch(:id))
# For compatibility with old markets.yml
# If state is not defined set it from enabled.
enabled = hash.delete(:enabled)
hash[:state] ||= enabled ? :enabled : :disabled
# For compatibility with old markets.yml we keep legacy-new keys mapping.
# New key value has higher priority than legacy.
legacy_keys_mappings = { ask_unit: :base_unit,
bid_unit: :quote_unit,
ask_precision: :amount_precision,
bid_precision: :price_precision,
min_ask_price: :min_price,
max_bid_price: :max_price,
min_ask_amount: :min_amount,
min_bid_amount: :min_amount }
legacy_keys_mappings.each do |old_key, new_key|
legacy_key_value = hash.delete(old_key)
hash[new_key] ||= legacy_key_value
end
# Select engine with provided name
engine = Engine.find_by(name: hash[:engine_name])
if engine.present?
hash.delete :engine_name
hash[:engine_id] = engine.id
Market.create!(hash)
else
Rails.logger.error "Engine doesn't exist"
end
end
end
end
desc 'Adds missing wallets to database defined at config/seed/wallets.yml.'
task wallets: :environment do
Wallet.transaction do
YAML.load_file(Rails.root.join('config/seed/wallets.yml')).each do |hash|
next if Wallet.exists?(name: hash.fetch('name'))
if hash['currency_ids'].is_a?(String)
hash['currency_ids'] = hash['currency_ids'].split(',')
end
Wallet.create!(hash)
end
end
end
desc 'Adds missing trading_fees to database defined at config/seed/trading_fees.yml.'
task trading_fees: :environment do
TradingFee.transaction do
YAML.load_file(Rails.root.join('config/seed/trading_fees.yml')).each do |hash|
next if TradingFee.exists?(market_id: hash.fetch('market_id'), group: hash.fetch('group'))
TradingFee.create!(hash)
end
end
end
desc 'Adds missing whitelisted_smart_contracts to database defined at config/seed/whitelisted_smart_contracts.yml.'
task whitelisted_smart_contracts: :environment do
WhitelistedSmartContract.transaction do
YAML.load_file(Rails.root.join('config/seed/whitelisted_smart_contracts.yml')).each do |hash|
next if WhitelistedSmartContract.exists?(address: hash.fetch('address'), blockchain_key: hash.fetch('blockchain_key'))
WhitelistedSmartContract.create!(hash)
end
end
end
desc 'Adds missing member for test'
task members: :environment do
Member.transaction do
YAML.load_file(Rails.root.join('config/seed/members.yml')).each do |hash|
next if Member.exists?(id: hash.fetch('id'))
Member.create!(hash)
end
end
end
desc 'Adds deposit limits'
task deposit_limits: :environment do
::DepositLimit.transaction do
YAML.load_file(Rails.root.join('config/seed/deposit_limit.yml')).each do |hash|
next if ::DepositLimit.exists?(id: hash.fetch('id'))
::DepositLimit.create!(hash)
end
end
end
end

View File

@@ -0,0 +1,10 @@
desc 'Select and update taker_type base on taker_order'
task assign_taker_type: :environment do
Trade.where(taker_type: '').find_in_batches do |batch|
ActiveRecord::Base.transaction do
batch.each do |t|
t.update_attribute(:taker_type, t.taker_order.side) if t.taker_order.present?
end
end
end
end

View File

@@ -0,0 +1,20 @@
namespace :trading_fee_test do
task :amqp, [:config_load_path] => [:environment] do |_t, args|
args.with_defaults(:config_load_path => 'config/bench/trade_execution.yml')
benches =
YAML.load_file(Rails.root.join(args[:config_load_path]))
.map(&:deep_symbolize_keys)
.each_with_object([]) do |config, memo|
Kernel.pp config
trade_execution = ::Bench::TradeExecution::AMQP.new(config)
trade_execution.run_fee!
memo << trade_execution
trade_execution.save_report
Kernel.puts "Sleep before next bench"
sleep 5
end
benches.each {|b| Kernel.pp b.result}
end
end