peatio-jruby 2.6.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.drone.yml +29 -0
- data/.gitignore +16 -0
- data/.rspec +3 -0
- data/.rubocop.yml +148 -0
- data/.simplecov +17 -0
- data/.tool-versions +1 -0
- data/.travis.yml +18 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +198 -0
- data/README.md +47 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/peatio +12 -0
- data/bin/setup +8 -0
- data/lib/peatio.rb +52 -0
- data/lib/peatio/adapter_registry.rb +25 -0
- data/lib/peatio/auth/error.rb +18 -0
- data/lib/peatio/auth/jwt_authenticator.rb +127 -0
- data/lib/peatio/block.rb +29 -0
- data/lib/peatio/blockchain/abstract.rb +161 -0
- data/lib/peatio/blockchain/error.rb +37 -0
- data/lib/peatio/blockchain/registry.rb +16 -0
- data/lib/peatio/command/base.rb +11 -0
- data/lib/peatio/command/db.rb +20 -0
- data/lib/peatio/command/inject.rb +13 -0
- data/lib/peatio/command/root.rb +14 -0
- data/lib/peatio/command/security.rb +29 -0
- data/lib/peatio/command/service.rb +40 -0
- data/lib/peatio/error.rb +18 -0
- data/lib/peatio/executor.rb +64 -0
- data/lib/peatio/injectors/peatio_events.rb +240 -0
- data/lib/peatio/logger.rb +39 -0
- data/lib/peatio/metrics/server.rb +15 -0
- data/lib/peatio/mq/client.rb +51 -0
- data/lib/peatio/ranger/connection.rb +117 -0
- data/lib/peatio/ranger/events.rb +11 -0
- data/lib/peatio/ranger/router.rb +234 -0
- data/lib/peatio/ranger/web_socket.rb +68 -0
- data/lib/peatio/security/key_generator.rb +26 -0
- data/lib/peatio/sql/client.rb +19 -0
- data/lib/peatio/sql/schema.rb +72 -0
- data/lib/peatio/transaction.rb +122 -0
- data/lib/peatio/upstream/base.rb +116 -0
- data/lib/peatio/upstream/registry.rb +14 -0
- data/lib/peatio/version.rb +3 -0
- data/lib/peatio/wallet/abstract.rb +189 -0
- data/lib/peatio/wallet/error.rb +37 -0
- data/lib/peatio/wallet/registry.rb +16 -0
- data/peatio.gemspec +59 -0
- 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
|