peatio-jruby 2.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|