peatio-jruby 2.6.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.drone.yml +29 -0
  3. data/.gitignore +16 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +148 -0
  6. data/.simplecov +17 -0
  7. data/.tool-versions +1 -0
  8. data/.travis.yml +18 -0
  9. data/Gemfile +8 -0
  10. data/Gemfile.lock +198 -0
  11. data/README.md +47 -0
  12. data/Rakefile +6 -0
  13. data/bin/console +14 -0
  14. data/bin/peatio +12 -0
  15. data/bin/setup +8 -0
  16. data/lib/peatio.rb +52 -0
  17. data/lib/peatio/adapter_registry.rb +25 -0
  18. data/lib/peatio/auth/error.rb +18 -0
  19. data/lib/peatio/auth/jwt_authenticator.rb +127 -0
  20. data/lib/peatio/block.rb +29 -0
  21. data/lib/peatio/blockchain/abstract.rb +161 -0
  22. data/lib/peatio/blockchain/error.rb +37 -0
  23. data/lib/peatio/blockchain/registry.rb +16 -0
  24. data/lib/peatio/command/base.rb +11 -0
  25. data/lib/peatio/command/db.rb +20 -0
  26. data/lib/peatio/command/inject.rb +13 -0
  27. data/lib/peatio/command/root.rb +14 -0
  28. data/lib/peatio/command/security.rb +29 -0
  29. data/lib/peatio/command/service.rb +40 -0
  30. data/lib/peatio/error.rb +18 -0
  31. data/lib/peatio/executor.rb +64 -0
  32. data/lib/peatio/injectors/peatio_events.rb +240 -0
  33. data/lib/peatio/logger.rb +39 -0
  34. data/lib/peatio/metrics/server.rb +15 -0
  35. data/lib/peatio/mq/client.rb +51 -0
  36. data/lib/peatio/ranger/connection.rb +117 -0
  37. data/lib/peatio/ranger/events.rb +11 -0
  38. data/lib/peatio/ranger/router.rb +234 -0
  39. data/lib/peatio/ranger/web_socket.rb +68 -0
  40. data/lib/peatio/security/key_generator.rb +26 -0
  41. data/lib/peatio/sql/client.rb +19 -0
  42. data/lib/peatio/sql/schema.rb +72 -0
  43. data/lib/peatio/transaction.rb +122 -0
  44. data/lib/peatio/upstream/base.rb +116 -0
  45. data/lib/peatio/upstream/registry.rb +14 -0
  46. data/lib/peatio/version.rb +3 -0
  47. data/lib/peatio/wallet/abstract.rb +189 -0
  48. data/lib/peatio/wallet/error.rb +37 -0
  49. data/lib/peatio/wallet/registry.rb +16 -0
  50. data/peatio.gemspec +59 -0
  51. metadata +480 -0
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Peatio::Ranger
4
+ def self.run(jwt_public_key, exchange_name, opts={})
5
+ host = opts[:ranger_host] || ENV["RANGER_HOST"] || "0.0.0.0"
6
+ port = opts[:ranger_port] || ENV["RANGER_PORT"] || "8081"
7
+
8
+ authenticator = Peatio::Auth::JWTAuthenticator.new(jwt_public_key)
9
+
10
+ logger = Peatio::Logger.logger
11
+ logger.info "Starting the server on port #{port}"
12
+
13
+ client = Peatio::MQ::Client.new
14
+ router = Peatio::Ranger::Router.new(opts[:registry])
15
+ client.subscribe(exchange_name, &router.method(:on_message))
16
+
17
+ if opts[:display_stats]
18
+ EM.add_periodic_timer(opts[:stats_period]) do
19
+ Peatio::Logger.logger.info { router.stats }
20
+ Peatio::Logger.logger.debug { router.debug }
21
+ end
22
+ end
23
+
24
+ EM::WebSocket.start(host: host, port: port, secure: false) do |socket|
25
+ connection = Peatio::Ranger::Connection.new(router, socket, logger)
26
+ socket.onopen do |hs|
27
+ connection.handshake(authenticator, hs)
28
+ router.on_connection_open(connection)
29
+ end
30
+
31
+ socket.onmessage do |msg|
32
+ connection.handle(msg)
33
+ end
34
+
35
+ socket.onping do |value|
36
+ logger.debug { "Received ping: #{value}" }
37
+ end
38
+
39
+ socket.onclose do
40
+ logger.debug { "Websocket connection closed" }
41
+ router.on_connection_close(connection)
42
+ end
43
+
44
+ socket.onerror do |e|
45
+ logger.error { "WebSocket Error: #{e.message}\n" + e.backtrace.join("\n") }
46
+ end
47
+ end
48
+ end
49
+
50
+ def self.run!(jwt_public_key, exchange_name, opts={})
51
+ metrics_host = opts[:metrics_host] || ENV["METRICS_HOST"] || "0.0.0.0"
52
+ metrics_port = opts[:metrics_port] || ENV["METRICS_PORT"] || "8082"
53
+
54
+ EM.run do
55
+ run(jwt_public_key, exchange_name, opts)
56
+
57
+ if opts[:registry]
58
+ thin = Rack::Handler.get("thin")
59
+ thin.run(Peatio::Metrics::Server.app(opts[:registry]), Host: metrics_host, Port: metrics_port)
60
+ end
61
+
62
+ trap("INT") do
63
+ puts "\nSIGINT received, stopping ranger..."
64
+ EM.stop
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,26 @@
1
+ require 'fileutils'
2
+
3
+ module Peatio::Security
4
+ class KeyGenerator
5
+
6
+ attr_reader :public, :private
7
+
8
+ def initialize
9
+ OpenSSL::PKey::RSA.generate(2048).tap do |pkey|
10
+ @public = pkey.public_key.to_pem
11
+ @private = pkey.to_pem
12
+ end
13
+ end
14
+
15
+ def save(folder)
16
+ FileUtils.mkdir_p(folder) unless File.exists?(folder)
17
+
18
+ write(File.join(folder, 'rsa-key'), @private)
19
+ write(File.join(folder, 'rsa-key.pub'), @public)
20
+ end
21
+
22
+ def write(filename, text)
23
+ File.open(filename, 'w') { |file| file.write(text) }
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,19 @@
1
+ module Peatio::Sql
2
+ class Client
3
+ attr_accessor :client, :config
4
+
5
+ def initialize
6
+ @config = {
7
+ host: ENV["DATABASE_HOST"] || "localhost",
8
+ username: ENV["DATABASE_USER"] || "root",
9
+ password: ENV["DATABASE_PASS"] || "",
10
+ port: ENV["DATABASE_PORT"] || "3306",
11
+ database: ENV["DATABASE_NAME"] || "peatio_development",
12
+ }
13
+ end
14
+
15
+ def connect
16
+ @client = Mysql2::Client.new(config)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,72 @@
1
+ module Peatio::Sql
2
+ class Schema
3
+ attr_accessor :client
4
+
5
+ def initialize(sql_client)
6
+ @client = sql_client
7
+ end
8
+
9
+ def create_database(name)
10
+ client.query("CREATE DATABASE IF NOT EXISTS `#{ name }`;")
11
+ end
12
+
13
+ def create_tables(options = {})
14
+ statements = []
15
+ statements << "DROP TABLE IF EXISTS `operations`;" if options[:drop_if_exists]
16
+ statements << <<-EOF
17
+ CREATE TABLE IF NOT EXISTS `operations` (
18
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT,
19
+ code TINYINT UNSIGNED NOT NULL,
20
+ account_id INT UNSIGNED NOT NULL,
21
+ reference INT UNSIGNED NOT NULL,
22
+ debit DECIMAL(32, 16) NOT NULL,
23
+ credit DECIMAL(32, 16) NOT NULL,
24
+ created_at DATETIME NOT NULL,
25
+ updated_at DATETIME NOT NULL,
26
+ PRIMARY KEY (id),
27
+ INDEX `balance_key` (account_id, debit, credit)
28
+ ) ENGINE = InnoDB;
29
+ EOF
30
+
31
+ statements << "DROP TABLE IF EXISTS `orders`;" if options[:drop_if_exists]
32
+ statements << <<EOF
33
+ CREATE TABLE IF NOT EXISTS`orders` (
34
+ `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
35
+ `uid` INT(11) UNSIGNED NOT NULL,
36
+ `bid` VARCHAR(5) NOT NULL,
37
+ `ask` VARCHAR(5) NOT NULL,
38
+ `market` VARCHAR(10) NOT NULL,
39
+ `price` DECIMAL(32,16) DEFAULT NULL,
40
+ `volume` DECIMAL(32,16) NOT NULL,
41
+ `fee` DECIMAL(32,16) NOT NULL DEFAULT '0.0000000000000000',
42
+ `type` TINYINT UNSIGNED NOT NULL,
43
+ `state` TINYINT UNSIGNED NOT NULL,
44
+ `created_at` DATETIME NOT NULL,
45
+ `updated_at` DATETIME NOT NULL,
46
+ PRIMARY KEY (`id`)
47
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
48
+ EOF
49
+
50
+ statements << "DROP TABLE IF EXISTS `trades`;" if options[:drop_if_exists]
51
+ statements << <<EOF
52
+ CREATE TABLE IF NOT EXISTS `trades` (
53
+ `id` int(11) NOT NULL AUTO_INCREMENT,
54
+ `market` varchar(10) NOT NULL,
55
+ `volume` decimal(32,16) NOT NULL,
56
+ `price` decimal(32,16) NOT NULL,
57
+ `ask_id` int(11) NOT NULL,
58
+ `bid_id` int(11) NOT NULL,
59
+ `ask_uid` int(11) NOT NULL,
60
+ `bid_uid` int(11) NOT NULL,
61
+ `created_at` datetime NOT NULL,
62
+ `updated_at` datetime NOT NULL,
63
+ PRIMARY KEY (`id`)
64
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
65
+ EOF
66
+ statements.each do |statement|
67
+ puts statement
68
+ client.query(statement)
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,122 @@
1
+ require 'active_support/concern'
2
+ require 'active_support/core_ext/string/inquiry'
3
+ require 'active_support/core_ext/object/blank'
4
+ require 'active_model'
5
+
6
+ module Peatio #:nodoc:
7
+
8
+ # This class represents blockchain transaction.
9
+ #
10
+ # Using the instant of this class the peatio application will send/recieve
11
+ # income/outcome transactions from a peatio pluggable blockchain.
12
+ #
13
+ # @example
14
+ #
15
+ # Peatio::Transaction.new(
16
+ # {
17
+ # hash: '0x5d0ef9697a2f3ea561c9fbefb48e380a4cf3d26ad2be253177c472fdd0e8b486',
18
+ # txout: 1,
19
+ # to_address: '0x9af4f143cd5ecfba0fcdd863c5ef52d5ccb4f3e5',
20
+ # amount: 0.01,
21
+ # block_number: 7732274,
22
+ # currency_id: 'eth',
23
+ # status: 'success'
24
+ # }
25
+ # )
26
+ #
27
+ # @author
28
+ # Maksym Naichuk <naichuk.maks@gmail.com> (https://github.com/mnaichuk)
29
+ class Transaction
30
+ include ActiveModel::Model
31
+
32
+ # List of statuses supported by peatio.
33
+ #
34
+ # @note Statuses list:
35
+ #
36
+ # pending - the transaction is unconfirmed in the blockchain or
37
+ # wasn't created yet.
38
+ #
39
+ # success - the transaction is a successfull,
40
+ # the transaction amount has been successfully transferred
41
+ #
42
+ # failed - the transaction is failed in the blockchain.
43
+
44
+ STATUSES = %w[success pending failed].freeze
45
+
46
+ DEFAULT_STATUS = 'pending'.freeze
47
+
48
+ # @!attribute [rw] hash
49
+ # return [String] transaction hash
50
+ attr_accessor :hash
51
+
52
+ # @!attribute [rw] txout
53
+ # return [Integer] transaction number in send-to-many request
54
+ attr_accessor :txout
55
+
56
+ # @!attribute [rw] from_address
57
+ # return [Array<String>] transaction source addresses
58
+ attr_accessor :from_addresses
59
+
60
+ # @!attribute [rw] to_address
61
+ # return [String] transaction recepient address
62
+ attr_accessor :to_address
63
+
64
+ # @!attribute [rw] amount
65
+ # return [Decimal] amount of the transaction
66
+ attr_accessor :amount
67
+
68
+ # @!attribute [rw] block_number
69
+ # return [Integer] transaction block number
70
+ attr_accessor :block_number
71
+
72
+ # @!attribute [rw] currency_id
73
+ # return [String] transaction currency id
74
+ attr_accessor :currency_id
75
+
76
+ # @!attribute [rw] options
77
+ # return [JSON] transaction options
78
+ attr_accessor :options
79
+
80
+ validates :to_address,
81
+ :amount,
82
+ :currency_id,
83
+ :status,
84
+ presence: true
85
+
86
+ validates :hash,
87
+ :block_number,
88
+ presence: { if: -> (t){ t.status.failed? || t.status.success? } }
89
+
90
+ validates :txout,
91
+ presence: { if: -> (t){ t.status.success? } }
92
+
93
+ validates :block_number,
94
+ numericality: { greater_than_or_equal_to: 0, only_integer: true }
95
+
96
+ validates :amount,
97
+ numericality: { greater_than_or_equal_to: 0 }
98
+
99
+ validates :status, inclusion: { in: STATUSES }
100
+
101
+ def initialize(attributes={})
102
+ super
103
+ @status = @status.present? ? @status.to_s : DEFAULT_STATUS
104
+ end
105
+
106
+ # Status for specific transaction.
107
+ #
108
+ # @!method status
109
+ #
110
+ # @example
111
+ #
112
+ # status.failed? # true if transaction status 'failed'
113
+ # status.success? # true if transaction status 'success'
114
+ def status
115
+ @status&.inquiry
116
+ end
117
+
118
+ def status=(s)
119
+ @status = s.to_s
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Peatio
4
+ module Upstream
5
+ class Base
6
+ DEFAULT_DELAY = 1
7
+ WEBSOCKET_CONNECTION_RETRY_DELAY = 2
8
+
9
+ attr_accessor :logger
10
+
11
+ def initialize(config)
12
+ @host = config["rest"]
13
+ @adapter = config[:faraday_adapter] || :em_synchrony
14
+ @config = config
15
+ @ws_status = false
16
+ @market = config['source']
17
+ @target = config['target']
18
+ @public_trades_cb = []
19
+ @logger = Peatio::Logger.logger
20
+ @peatio_mq = config['amqp']
21
+ mount
22
+ end
23
+
24
+ def mount
25
+ @public_trades_cb << method(:on_trade)
26
+ end
27
+
28
+ def ws_connect
29
+ logger.info { "Websocket connecting to #{@ws_url}" }
30
+ raise "websocket url missing for account #{id}" unless @ws_url
31
+
32
+ @ws = Faye::WebSocket::Client.new(@ws_url)
33
+
34
+ @ws.on(:open) do |_e|
35
+ subscribe_trades(@target, @ws)
36
+ subscribe_orderbook(@target, @ws)
37
+ logger.info { "Websocket connected" }
38
+ end
39
+
40
+ @ws.on(:message) do |msg|
41
+ ws_read_message(msg)
42
+ end
43
+
44
+ @ws.on(:close) do |e|
45
+ @ws = nil
46
+ @ws_status = false
47
+ logger.error "Websocket disconnected: #{e.code} Reason: #{e.reason}"
48
+ Fiber.new do
49
+ EM::Synchrony.sleep(WEBSOCKET_CONNECTION_RETRY_DELAY)
50
+ ws_connect
51
+ end.resume
52
+ end
53
+ end
54
+
55
+ def ws_connect_public
56
+ ws_connect
57
+ end
58
+
59
+ def subscribe_trades(_market, _ws)
60
+ method_not_implemented
61
+ end
62
+
63
+ def subscribe_orderbook(_market, _ws)
64
+ method_not_implemented
65
+ end
66
+
67
+ def ws_read_public_message(msg)
68
+ logger.info { "received public message: #{msg}" }
69
+ end
70
+
71
+ def ws_read_message(msg)
72
+ logger.debug {"received websocket message: #{msg.data}" }
73
+
74
+ object = JSON.parse(msg.data)
75
+ ws_read_public_message(object)
76
+ end
77
+
78
+ def on_trade(trade)
79
+ logger.info { "Publishing trade event: #{trade.inspect}" }
80
+ @peatio_mq.enqueue_event("public", @market, "trades", {trades: [trade]})
81
+ @peatio_mq.publish :trade, trade_json(trade), {
82
+ headers: {
83
+ type: :upstream,
84
+ market: @market,
85
+ }
86
+ }
87
+ end
88
+
89
+ def trade_json(trade)
90
+ trade.deep_symbolize_keys!
91
+ {
92
+ id: trade[:tid],
93
+ price: trade[:price],
94
+ amount: trade[:amount],
95
+ market_id: @market,
96
+ created_at: Time.at(trade[:date]).utc.iso8601,
97
+ taker_type: trade[:taker_type]
98
+ }
99
+ end
100
+
101
+ def notify_public_trade(trade)
102
+ @public_trades_cb.each {|cb| cb&.call(trade) }
103
+ end
104
+
105
+ def to_s
106
+ "Exchange::#{self.class} config: #{@opts}"
107
+ end
108
+
109
+ def build_error(response)
110
+ JSON.parse(response.body)
111
+ rescue StandardError => e
112
+ "Code: #{response.env.status} Message: #{response.env.reason_phrase}"
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Peatio
4
+ module Upstream
5
+ class << self
6
+ def registry
7
+ @registry ||= Registry.new
8
+ end
9
+
10
+ class Registry < Peatio::AdapterRegistry
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ module Peatio
2
+ VERSION = "2.6.2"
3
+ end