straight-server 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +8 -0
- data/Gemfile +3 -1
- data/Gemfile.lock +57 -47
- data/Gemfile.travis +26 -0
- data/README.md +175 -22
- data/Rakefile +7 -0
- data/VERSION +1 -1
- data/benchmark/addons.yml +15 -0
- data/benchmark/config.yml +78 -0
- data/benchmark/default_last_keychain_id +1 -0
- data/benchmark/server_secret +1 -0
- data/bin/goliath.log +6 -0
- data/bin/goliath.log_stdout.log +51 -0
- data/bin/straight-server-benchmark +68 -0
- data/db/migrations/003_add_payment_id_to_orders.rb +13 -0
- data/db/migrations/004_add_description_to_orders.rb +11 -0
- data/db/migrations/005_add_orders_expiration_period_to_gateways.rb +11 -0
- data/db/migrations/006_add_check_order_status_in_db_first_to_gateways.rb +11 -0
- data/db/migrations/007_add_active_switcher_to_gateways.rb +11 -0
- data/db/migrations/008_add_order_counters_to_gateways.rb +11 -0
- data/db/migrations/009_add_hashed_id_to_gateways.rb +18 -0
- data/examples/client/client.dart +5 -0
- data/examples/client/client.html +7 -15
- data/examples/client/client.js +15 -0
- data/lib/straight-server/config.rb +1 -1
- data/lib/straight-server/gateway.rb +241 -59
- data/lib/straight-server/initializer.rb +170 -44
- data/lib/straight-server/logger.rb +1 -1
- data/lib/straight-server/order.rb +74 -9
- data/lib/straight-server/orders_controller.rb +23 -6
- data/lib/straight-server/random_string.rb +18 -0
- data/lib/straight-server/server.rb +44 -17
- data/lib/straight-server/utils/hash_string_to_sym_keys.rb +24 -0
- data/lib/straight-server.rb +6 -3
- data/spec/.straight/config.yml +16 -0
- data/spec/.straight/server_secret +1 -0
- data/spec/fixtures/addons.yml +19 -0
- data/spec/fixtures/test_addon.rb +8 -0
- data/spec/lib/gateway_spec.rb +93 -13
- data/spec/lib/initializer_spec.rb +104 -0
- data/spec/lib/order_spec.rb +59 -0
- data/spec/lib/orders_controller_spec.rb +34 -1
- data/spec/lib/utils/hash_string_to_sym_keys.rb +18 -0
- data/spec/spec_helper.rb +10 -2
- data/straight-server.gemspec +36 -8
- data/templates/addons.yml +15 -0
- data/templates/config.yml +41 -0
- metadata +47 -5
@@ -4,19 +4,89 @@ module StraightServer
|
|
4
4
|
# in one of the classes below.
|
5
5
|
module GatewayModule
|
6
6
|
|
7
|
+
# Temporary fix for straight server benchmarking
|
8
|
+
@@redis = StraightServer::Config.redis[:connection] if StraightServer::Config.redis
|
9
|
+
@@websockets = {}
|
10
|
+
|
11
|
+
def fetch_transactions_for(address)
|
12
|
+
try_adapters(@blockchain_adapters) { |b| b.fetch_transactions_for(address) }
|
13
|
+
end
|
14
|
+
|
7
15
|
class InvalidSignature < Exception; end
|
8
16
|
class InvalidOrderId < Exception; end
|
9
17
|
class CallbackUrlBadResponse < Exception; end
|
10
18
|
class WebsocketExists < Exception; end
|
11
19
|
class WebsocketForCompletedOrder < Exception; end
|
20
|
+
class GatewayInactive < Exception; end
|
21
|
+
class NoBlockchainAdapters < Exception
|
22
|
+
def message
|
23
|
+
"No blockchain adapters were found! StraightServer cannot query the blockchain.\n" +
|
24
|
+
"Check your ~/.straight/config.yml file and make sure valid blockchain adapters\n" +
|
25
|
+
"are present."
|
26
|
+
end
|
27
|
+
end
|
28
|
+
class NoWebsocketsForNewGateway < Exception
|
29
|
+
def message
|
30
|
+
"You're trying to get access to websockets on a Gateway that hasn't been saved yet"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
class OrderCountersDisabled < Exception
|
34
|
+
def message
|
35
|
+
"Please enable order counting in config file! You can do is using the following option:\n\n" +
|
36
|
+
" count_orders: true\n\n" +
|
37
|
+
"and don't forget to provide Redis connection info by adding this to the config file as well:\n\n" +
|
38
|
+
" redis:\n" +
|
39
|
+
" host: localhost\n" +
|
40
|
+
" port: 6379\n" +
|
41
|
+
" db: null\n"
|
42
|
+
end
|
43
|
+
end
|
12
44
|
|
13
45
|
CALLBACK_URL_ATTEMPT_TIMEFRAME = 3600 # seconds
|
14
46
|
|
15
|
-
|
16
|
-
|
47
|
+
|
48
|
+
############# Initializers methods ########################################################
|
49
|
+
# We have separate methods, because with GatewayOnDB they are called from #after_initialize
|
50
|
+
# but in GatewayOnConfig they are called from #initialize intself.
|
51
|
+
# #########################################################################################
|
52
|
+
#
|
53
|
+
def initialize_exchange_rate_adapters
|
54
|
+
@exchange_rate_adapters ||= []
|
55
|
+
if self.exchange_rate_adapter_names
|
56
|
+
self.exchange_rate_adapter_names.each do |adapter|
|
57
|
+
begin
|
58
|
+
@exchange_rate_adapters << Straight::ExchangeRate.const_get("#{adapter}Adapter").instance
|
59
|
+
rescue NameError => e
|
60
|
+
puts "WARNING: No exchange rate adapter with the name #{a} was found!"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def initialize_blockchain_adapters
|
67
|
+
@blockchain_adapters = []
|
68
|
+
StraightServer::Config.blockchain_adapters.each do |a|
|
69
|
+
|
70
|
+
adapter = begin
|
71
|
+
Straight::Blockchain.const_get("#{a}Adapter")
|
72
|
+
rescue NameError
|
73
|
+
begin
|
74
|
+
Kernel.const_get(a)
|
75
|
+
rescue NameError
|
76
|
+
puts "WARNING: No blockchain adapter with the name #{a} was found!"
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
@blockchain_adapters << adapter.mainnet_adapter if adapter
|
82
|
+
end
|
83
|
+
raise NoBlockchainAdapters if @blockchain_adapters.empty?
|
84
|
+
end
|
85
|
+
|
86
|
+
def initialize_callbacks
|
17
87
|
# When the status of an order changes, we send an http request to the callback_url
|
18
88
|
# and also notify a websocket client (if present, of course).
|
19
|
-
@order_callbacks
|
89
|
+
@order_callbacks = [
|
20
90
|
lambda do |order|
|
21
91
|
StraightServer::Thread.new do
|
22
92
|
send_callback_http_request order
|
@@ -24,27 +94,25 @@ module StraightServer
|
|
24
94
|
end
|
25
95
|
end
|
26
96
|
]
|
97
|
+
end
|
27
98
|
|
28
|
-
|
29
|
-
Straight::Blockchain::BlockchainInfoAdapter.mainnet_adapter,
|
30
|
-
Straight::Blockchain::HelloblockIoAdapter.mainnet_adapter
|
31
|
-
]
|
32
|
-
|
33
|
-
@exchange_rate_adapters = []
|
99
|
+
def initialize_status_check_schedule
|
34
100
|
@status_check_schedule = Straight::GatewayModule::DEFAULT_STATUS_CHECK_SCHEDULE
|
35
|
-
@websockets = {}
|
36
|
-
|
37
|
-
super
|
38
|
-
initialize_exchange_rate_adapters # should always go after super
|
39
101
|
end
|
102
|
+
#
|
103
|
+
############# END OF Initializers methods ##################################################
|
104
|
+
|
40
105
|
|
41
106
|
# Creates a new order and saves into the DB. Checks if the MD5 hash
|
42
107
|
# is correct first.
|
43
108
|
def create_order(attrs={})
|
109
|
+
|
110
|
+
raise GatewayInactive unless self.active
|
111
|
+
|
44
112
|
StraightServer.logger.info "Creating new order with attrs: #{attrs}"
|
45
113
|
signature = attrs.delete(:signature)
|
46
|
-
raise InvalidOrderId if check_signature && (attrs[:id].nil? || attrs[:id].to_i <= 0)
|
47
114
|
if !check_signature || sign_with_secret(attrs[:id]) == signature
|
115
|
+
raise InvalidOrderId if check_signature && (attrs[:id].nil? || attrs[:id].to_i <= 0)
|
48
116
|
order = order_for_keychain_id(
|
49
117
|
amount: attrs[:amount],
|
50
118
|
keychain_id: increment_last_keychain_id!,
|
@@ -75,47 +143,69 @@ module StraightServer
|
|
75
143
|
end
|
76
144
|
|
77
145
|
def add_websocket_for_order(ws, order)
|
78
|
-
raise WebsocketExists unless
|
146
|
+
raise WebsocketExists unless websockets[order.id].nil?
|
79
147
|
raise WebsocketForCompletedOrder unless order.status < 2
|
80
148
|
StraightServer.logger.info "Opening ws connection for #{order.id}"
|
81
149
|
ws.on(:close) do |event|
|
82
|
-
|
150
|
+
websockets.delete(order.id)
|
83
151
|
StraightServer.logger.info "Closing ws connection for #{order.id}"
|
84
152
|
end
|
85
|
-
|
153
|
+
websockets[order.id] = ws
|
86
154
|
ws
|
87
155
|
end
|
88
156
|
|
157
|
+
def websockets
|
158
|
+
raise NoWebsocketsForNewGateway unless self.id
|
159
|
+
@@websockets[self.id]
|
160
|
+
end
|
161
|
+
|
89
162
|
def send_order_to_websocket_client(order)
|
90
|
-
if ws =
|
163
|
+
if ws = websockets[order.id]
|
91
164
|
ws.send(order.to_json)
|
92
165
|
ws.close
|
93
166
|
end
|
94
167
|
end
|
95
168
|
|
96
|
-
def
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
@exchange_rate_adapters << Kernel.const_get("Straight::ExchangeRate::#{adapter}Adapter").new
|
101
|
-
rescue NameError
|
102
|
-
raise NameError, "No such adapter exists: Straight::ExchangeRate::#{adapter}Adapter"
|
103
|
-
end
|
104
|
-
end
|
169
|
+
def sign_with_secret(content, level: 1)
|
170
|
+
result = content.to_s
|
171
|
+
level.times do
|
172
|
+
result = OpenSSL::HMAC.digest('sha256', secret, result).unpack("H*").first
|
105
173
|
end
|
174
|
+
result
|
106
175
|
end
|
107
176
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
h = HMAC::SHA1.new(secret)
|
114
|
-
h << result
|
115
|
-
result = h.hexdigest
|
116
|
-
end
|
117
|
-
result
|
177
|
+
def order_status_changed(order)
|
178
|
+
statuses = Order::STATUSES.invert
|
179
|
+
if StraightServer::Config.count_orders
|
180
|
+
increment_order_counter!(statuses[order.old_status], -1) if order.old_status
|
181
|
+
increment_order_counter!(statuses[order.status])
|
118
182
|
end
|
183
|
+
super
|
184
|
+
end
|
185
|
+
|
186
|
+
def order_counters(reload: false)
|
187
|
+
return @order_counters if @order_counters && !reload
|
188
|
+
@order_counters = {
|
189
|
+
new: get_order_counter(:new),
|
190
|
+
unconfirmed: get_order_counter(:unconfirmed),
|
191
|
+
paid: get_order_counter(:paid),
|
192
|
+
underpaid: get_order_counter(:underpaid),
|
193
|
+
overpaid: get_order_counter(:overpaid),
|
194
|
+
expired: get_order_counter(:expired)
|
195
|
+
}
|
196
|
+
end
|
197
|
+
|
198
|
+
def get_order_counter(counter_name)
|
199
|
+
raise OrderCountersDisabled unless StraightServer::Config.count_orders
|
200
|
+
@@redis.get("#{StraightServer::Config.redis[:prefix]}:gateway_#{id}:#{counter_name}_orders_counter").to_i || 0
|
201
|
+
end
|
202
|
+
|
203
|
+
def increment_order_counter!(counter_name, by=1)
|
204
|
+
raise OrderCountersDisabled unless StraightServer::Config.count_orders
|
205
|
+
@@redis.incrby("#{StraightServer::Config.redis[:prefix]}:gateway_#{id}:#{counter_name}_orders_counter", by)
|
206
|
+
end
|
207
|
+
|
208
|
+
private
|
119
209
|
|
120
210
|
# Tries to send a callback HTTP request to the resource specified
|
121
211
|
# in the #callback_url. If it fails for any reason, it keeps trying for an hour (3600 seconds)
|
@@ -141,7 +231,7 @@ module StraightServer
|
|
141
231
|
sleep(delay)
|
142
232
|
send_callback_http_request(order, delay: delay*2)
|
143
233
|
else
|
144
|
-
StraightServer.logger.warn "Callback request for order #{order.id}
|
234
|
+
StraightServer.logger.warn "Callback request for order #{order.id} failed, see order's #callback_response field for details"
|
145
235
|
end
|
146
236
|
end
|
147
237
|
|
@@ -157,6 +247,75 @@ module StraightServer
|
|
157
247
|
include GatewayModule
|
158
248
|
plugin :timestamps, create: :created_at, update: :updated_at
|
159
249
|
plugin :serialization, :marshal, :exchange_rate_adapter_names
|
250
|
+
plugin :serialization, :marshal
|
251
|
+
plugin :after_initialize
|
252
|
+
|
253
|
+
def self.find_by_hashed_id(s)
|
254
|
+
self.where(hashed_id: s).first
|
255
|
+
end
|
256
|
+
|
257
|
+
def before_create
|
258
|
+
super
|
259
|
+
encrypt_secret
|
260
|
+
end
|
261
|
+
|
262
|
+
def after_create
|
263
|
+
@@websockets[self.id] = {}
|
264
|
+
update(hashed_id: OpenSSL::HMAC.digest('sha256', Config.server_secret, self.id.to_s).unpack("H*").first)
|
265
|
+
end
|
266
|
+
|
267
|
+
def after_initialize
|
268
|
+
@status_check_schedule = Straight::GatewayModule::DEFAULT_STATUS_CHECK_SCHEDULE
|
269
|
+
@@websockets[self.id] ||= {} if self.id
|
270
|
+
initialize_callbacks
|
271
|
+
initialize_exchange_rate_adapters
|
272
|
+
initialize_blockchain_adapters
|
273
|
+
initialize_status_check_schedule
|
274
|
+
end
|
275
|
+
|
276
|
+
# We cannot allow to store gateway secret in a DB plaintext, this would be completetly unsecure.
|
277
|
+
# Althougth we use symmetrical encryption here and store the encryption key in the
|
278
|
+
# server's in a special file (~/.straight/server_secret), which in turn can also be stolen,
|
279
|
+
# this is still marginally better than doing nothing.
|
280
|
+
#
|
281
|
+
# Also, server admnistrators now have the freedom of developing their own strategy
|
282
|
+
# of storing that secret - it doesn't have to be stored on the same machine.
|
283
|
+
def secret
|
284
|
+
decrypt_secret
|
285
|
+
end
|
286
|
+
|
287
|
+
def self.find_by_id(id)
|
288
|
+
self[id]
|
289
|
+
end
|
290
|
+
|
291
|
+
private
|
292
|
+
|
293
|
+
def encrypt_secret
|
294
|
+
cipher = OpenSSL::Cipher::AES.new(128, :CBC)
|
295
|
+
cipher.encrypt
|
296
|
+
cipher.key = OpenSSL::HMAC.digest('sha256', 'nonce', Config.server_secret).unpack("H*").first[0,16]
|
297
|
+
cipher.iv = iv = OpenSSL::HMAC.digest('sha256', 'nonce', "#{self.id}#{Config.server_secret}").unpack("H*").first[0,16]
|
298
|
+
encrypted = cipher.update(self[:secret]) << cipher.final()
|
299
|
+
base64_encrypted = Base64.strict_encode64(encrypted).encode('utf-8')
|
300
|
+
result = "#{iv}:#{base64_encrypted}"
|
301
|
+
|
302
|
+
# Check whether we can decrypt. It should not be possible to encrypt the
|
303
|
+
# gateway secret unless we are sure we can decrypt it.
|
304
|
+
if decrypt_secret(result) == self[:secret]
|
305
|
+
self.secret = result
|
306
|
+
else
|
307
|
+
raise "Decrypted and original secrets don't match! Cannot proceed with writing the encrypted gateway secret."
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
def decrypt_secret(encrypted_field=self[:secret])
|
312
|
+
decipher = OpenSSL::Cipher::AES.new(128, :CBC)
|
313
|
+
iv, encrypted = encrypted_field.split(':')
|
314
|
+
decipher.decrypt
|
315
|
+
decipher.key = OpenSSL::HMAC.digest('sha256', 'nonce', Config.server_secret).unpack("H*").first[0,16]
|
316
|
+
decipher.iv = iv
|
317
|
+
decipher.update(Base64.decode64(encrypted)) + decipher.final
|
318
|
+
end
|
160
319
|
|
161
320
|
end
|
162
321
|
|
@@ -192,19 +351,35 @@ module StraightServer
|
|
192
351
|
attr_accessor :id
|
193
352
|
|
194
353
|
attr_accessor :exchange_rate_adapter_names
|
354
|
+
attr_accessor :orders_expiration_period
|
355
|
+
|
356
|
+
# This affects whether it is possible to create a new order with the gateway.
|
357
|
+
# If it's set to false, then it won't be possible to create a new order, but
|
358
|
+
# it will keep checking on the existing ones.
|
359
|
+
attr_accessor :active
|
360
|
+
|
361
|
+
def self.find_by_hashed_id(s)
|
362
|
+
self.find_by_id(s)
|
363
|
+
end
|
364
|
+
|
365
|
+
def initialize
|
366
|
+
initialize_callbacks
|
367
|
+
initialize_exchange_rate_adapters
|
368
|
+
initialize_blockchain_adapters
|
369
|
+
initialize_status_check_schedule
|
370
|
+
end
|
195
371
|
|
196
372
|
# Because this is a config based gateway, we only save last_keychain_id
|
197
373
|
# and nothing more.
|
198
374
|
def save
|
199
|
-
|
375
|
+
save_last_keychain_id!
|
200
376
|
end
|
201
377
|
|
202
378
|
# Loads last_keychain_id from a file in the .straight dir.
|
203
379
|
# If the file doesn't exist, we create it. Later, whenever an attribute is updated,
|
204
380
|
# we save it to the file.
|
205
381
|
def load_last_keychain_id!
|
206
|
-
@last_keychain_id_file
|
207
|
-
"/#{name}_last_keychain_id"
|
382
|
+
@last_keychain_id_file ||= StraightServer::Initializer::ConfigDir.path + "/#{name}_last_keychain_id"
|
208
383
|
if File.exists?(@last_keychain_id_file)
|
209
384
|
self.last_keychain_id = File.read(@last_keychain_id_file).to_i
|
210
385
|
else
|
@@ -213,6 +388,17 @@ module StraightServer
|
|
213
388
|
end
|
214
389
|
end
|
215
390
|
|
391
|
+
def save_last_keychain_id!
|
392
|
+
@last_keychain_id_file ||= StraightServer::Initializer::ConfigDir.path + "/#{name}_last_keychain_id"
|
393
|
+
File.open(@last_keychain_id_file, 'w') {|f| f.write(last_keychain_id) }
|
394
|
+
end
|
395
|
+
|
396
|
+
# This method is a replacement for the Sequel's model one used in DB version of the gateway
|
397
|
+
# and it finds gateways using the index of @@gateways Array.
|
398
|
+
def self.find_by_id(id)
|
399
|
+
@@gateways[id.to_i-1]
|
400
|
+
end
|
401
|
+
|
216
402
|
# This will later be used in the #find_by_id. Because we don't use a DB,
|
217
403
|
# the id will actually be the index of an element in this Array. Thus,
|
218
404
|
# the order in which gateways follow in the config file is important.
|
@@ -223,27 +409,23 @@ module StraightServer
|
|
223
409
|
StraightServer::Config.gateways.each do |name, attrs|
|
224
410
|
i += 1
|
225
411
|
gateway = self.new
|
226
|
-
gateway.pubkey
|
227
|
-
gateway.confirmations_required
|
228
|
-
gateway.order_class
|
229
|
-
gateway.secret
|
230
|
-
gateway.check_signature
|
231
|
-
gateway.callback_url
|
232
|
-
gateway.default_currency
|
233
|
-
gateway.
|
234
|
-
gateway.
|
412
|
+
gateway.pubkey = attrs['pubkey']
|
413
|
+
gateway.confirmations_required = attrs['confirmations_required'].to_i
|
414
|
+
gateway.order_class = attrs['order_class']
|
415
|
+
gateway.secret = attrs['secret']
|
416
|
+
gateway.check_signature = attrs['check_signature']
|
417
|
+
gateway.callback_url = attrs['callback_url']
|
418
|
+
gateway.default_currency = attrs['default_currency']
|
419
|
+
gateway.orders_expiration_period = attrs['orders_expiration_period']
|
420
|
+
gateway.active = attrs['active']
|
421
|
+
gateway.name = name
|
422
|
+
gateway.id = i
|
235
423
|
gateway.exchange_rate_adapter_names = attrs['exchange_rate_adapters']
|
236
424
|
gateway.initialize_exchange_rate_adapters
|
237
425
|
gateway.load_last_keychain_id!
|
426
|
+
@@websockets[i] = {}
|
238
427
|
@@gateways << gateway
|
239
|
-
end
|
240
|
-
|
241
|
-
|
242
|
-
# This method is a replacement for the Sequel's model one used in DB version of the gateway
|
243
|
-
# and it finds gateways using the index of @@gateways Array.
|
244
|
-
def self.find_by_id(id)
|
245
|
-
@@gateways[id.to_i-1]
|
246
|
-
end
|
428
|
+
end if StraightServer::Config.gateways
|
247
429
|
|
248
430
|
end
|
249
431
|
|
@@ -251,7 +433,7 @@ module StraightServer
|
|
251
433
|
# so they can simply start using a single gateway on their machines, a gateway which attributes are defined
|
252
434
|
# in a config file instead of a DB. That way they don't need special tools to access the DB and create
|
253
435
|
# a gateway, but can simply edit the config file.
|
254
|
-
Gateway = if StraightServer::Config.gateways_source
|
436
|
+
Gateway = if StraightServer::Config.gateways_source == 'config'
|
255
437
|
GatewayOnConfig
|
256
438
|
else
|
257
439
|
GatewayOnDB
|
@@ -2,76 +2,202 @@ module StraightServer
|
|
2
2
|
|
3
3
|
module Initializer
|
4
4
|
|
5
|
-
GEM_ROOT
|
6
|
-
|
5
|
+
GEM_ROOT = File.expand_path('../..', File.dirname(__FILE__))
|
6
|
+
|
7
|
+
module ConfigDir
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
# Determine config dir or set default. Useful when we want to
|
12
|
+
# have different settings for production or staging or development environments.
|
13
|
+
def set!(path=nil)
|
14
|
+
@@config_dir = path and return if path
|
15
|
+
@@config_dir = ENV['HOME'] + '/.straight'
|
16
|
+
ARGV.each do |a|
|
17
|
+
if a =~ /\A--config-dir=.+/
|
18
|
+
@@config_dir = File.expand_path(a.sub('--config-dir=', ''))
|
19
|
+
break
|
20
|
+
elsif a =~ /\A-c .+/
|
21
|
+
@@config_dir = File.expand_path(a.sub('-c ', ''))
|
22
|
+
break
|
23
|
+
end
|
24
|
+
end
|
25
|
+
puts "Setting config dir to #{@@config_dir}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def path
|
29
|
+
@@config_dir
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
7
35
|
|
8
36
|
def prepare
|
9
|
-
|
37
|
+
ConfigDir.set!
|
38
|
+
create_config_files
|
10
39
|
read_config_file
|
11
40
|
create_logger
|
12
41
|
connect_to_db
|
13
|
-
run_migrations
|
42
|
+
run_migrations if migrations_pending?
|
43
|
+
setup_redis_connection if StraightServer::Config.count_orders
|
44
|
+
initialize_routes
|
14
45
|
end
|
15
46
|
|
16
|
-
|
47
|
+
def add_route(path, &block)
|
48
|
+
@routes[path] = block
|
49
|
+
end
|
17
50
|
|
18
|
-
|
51
|
+
def create_config_files
|
52
|
+
FileUtils.mkdir_p(ConfigDir.path) unless File.exist?(ConfigDir.path)
|
53
|
+
|
54
|
+
unless File.exist?(ConfigDir.path + '/addons.yml')
|
55
|
+
puts "\e[1;33mNOTICE!\e[0m \e[33mNo file ~/.straight/addons.yml was found. Created an empty sample for you.\e[0m"
|
56
|
+
puts "No need to restart until you actually list your addons there. Now will continue loading StraightServer."
|
57
|
+
FileUtils.cp(GEM_ROOT + '/templates/addons.yml', ENV['HOME'] + '/.straight/')
|
58
|
+
end
|
59
|
+
|
60
|
+
unless File.exist?(ConfigDir.path + '/server_secret')
|
61
|
+
puts "\e[1;33mNOTICE!\e[0m \e[33mNo file ~/.straight/server_secret was found. Created one for you.\e[0m"
|
62
|
+
puts "No need to restart so far. Now will continue loading StraightServer."
|
63
|
+
File.open(ConfigDir.path + '/server_secret', "w") do |f|
|
64
|
+
f.puts String.random(16)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
unless File.exist?(ConfigDir.path + '/config.yml')
|
19
69
|
puts "\e[1;33mWARNING!\e[0m \e[33mNo file ~/.straight/config was found. Created a sample one for you.\e[0m"
|
20
70
|
puts "You should edit it and try starting the server again.\n"
|
21
71
|
|
22
|
-
FileUtils.mkdir_p(STRAIGHT_CONFIG_PATH) unless File.exist?(STRAIGHT_CONFIG_PATH)
|
23
72
|
FileUtils.cp(GEM_ROOT + '/templates/config.yml', ENV['HOME'] + '/.straight/')
|
24
73
|
puts "Shutting down now.\n\n"
|
25
74
|
exit
|
26
75
|
end
|
27
76
|
|
28
|
-
|
29
|
-
YAML.load_file(STRAIGHT_CONFIG_PATH + '/config.yml').each do |k,v|
|
30
|
-
StraightServer::Config.send(k + '=', v)
|
31
|
-
end
|
32
|
-
end
|
77
|
+
end
|
33
78
|
|
34
|
-
|
79
|
+
def read_config_file
|
80
|
+
YAML.load_file(ConfigDir.path + '/config.yml').each do |k,v|
|
81
|
+
StraightServer::Config.send(k + '=', v)
|
82
|
+
end
|
83
|
+
StraightServer::Config.server_secret = File.read(ConfigDir.path + '/server_secret').chomp
|
84
|
+
end
|
35
85
|
|
36
|
-
|
37
|
-
db_config = StraightServer::Config.db.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
|
86
|
+
def connect_to_db
|
38
87
|
|
39
|
-
|
40
|
-
|
41
|
-
else
|
42
|
-
db_config[:name]
|
43
|
-
end
|
88
|
+
# symbolize keys for convenience
|
89
|
+
db_config = StraightServer::Config.db.keys_to_sym
|
44
90
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
"#{db_config[:host]}#{(":" if db_config[:port])}" +
|
50
|
-
"#{db_config[:port]}#{("/" if db_config[:host] || db_config[:port])}" +
|
51
|
-
"#{db_name}"
|
52
|
-
)
|
91
|
+
db_name = if db_config[:adapter] == 'sqlite'
|
92
|
+
ConfigDir.path + "/" + db_config[:name]
|
93
|
+
else
|
94
|
+
db_config[:name]
|
53
95
|
end
|
54
96
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
97
|
+
StraightServer.db_connection = Sequel.connect(
|
98
|
+
"#{db_config[:adapter]}://" +
|
99
|
+
"#{db_config[:user]}#{(":" if db_config[:user])}" +
|
100
|
+
"#{db_config[:password]}#{("@" if db_config[:user] || db_config[:password])}" +
|
101
|
+
"#{db_config[:host]}#{(":" if db_config[:port])}" +
|
102
|
+
"#{db_config[:port]}#{("/" if db_config[:host] || db_config[:port])}" +
|
103
|
+
"#{db_name}"
|
104
|
+
)
|
105
|
+
end
|
106
|
+
|
107
|
+
def run_migrations
|
108
|
+
print "\nPending migrations for the database detected. Migrating..."
|
109
|
+
Sequel::Migrator.run(StraightServer.db_connection, GEM_ROOT + '/db/migrations/')
|
110
|
+
print "done\n\n"
|
111
|
+
end
|
112
|
+
|
113
|
+
def migrations_pending?
|
114
|
+
!Sequel::Migrator.is_current?(StraightServer.db_connection, GEM_ROOT + '/db/migrations/')
|
115
|
+
end
|
116
|
+
|
117
|
+
def create_logger
|
118
|
+
require_relative 'logger'
|
119
|
+
StraightServer.logger = StraightServer::Logger.new(
|
120
|
+
log_level: ::Logger.const_get(Config.logmaster['log_level'].upcase),
|
121
|
+
file: ConfigDir.path + '/' + Config.logmaster['file'],
|
122
|
+
raise_exception: Config.logmaster['raise_exception'],
|
123
|
+
name: Config.logmaster['name'],
|
124
|
+
email_config: Config.logmaster['email_config']
|
125
|
+
)
|
126
|
+
end
|
60
127
|
|
61
|
-
|
62
|
-
|
128
|
+
def initialize_routes
|
129
|
+
@routes = {}
|
130
|
+
add_route /\A\/gateways\/.+?\/orders(\/.+)?\Z/ do |env|
|
131
|
+
controller = OrdersController.new(env)
|
132
|
+
controller.response
|
63
133
|
end
|
134
|
+
end
|
64
135
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
136
|
+
# Loads addon modules into StraightServer::Server. To be useful,
|
137
|
+
# an addon most probably has to implement self.extended(server) callback.
|
138
|
+
# That way, it can access the server object and, for example, add routes
|
139
|
+
# with StraightServer::Server#add_route.
|
140
|
+
#
|
141
|
+
# Addon modules can be both rubygems or files under ~/.straight/addons/.
|
142
|
+
# If ~/.straight/addons.yml contains a 'path' key for a particular addon, then it means
|
143
|
+
# the addon is placed under the ~/.straight/addons/. If not, it is assumed it
|
144
|
+
# is already in the LOAD_PATH somehow, with rubygems for example.
|
145
|
+
def load_addons
|
146
|
+
# load ~/.straight/addons.yml
|
147
|
+
addons = YAML.load_file(ConfigDir.path + '/addons.yml')
|
148
|
+
addons.each do |name, addon|
|
149
|
+
StraightServer.logger.info "Loading #{name} addon"
|
150
|
+
if addon['path'] # First, check the ~/.straight/addons dir
|
151
|
+
require ConfigDir.path + '/' + addon['path']
|
152
|
+
else # then assume it's already loaded using rubygems
|
153
|
+
require name
|
154
|
+
end
|
155
|
+
# extending the current server object with the addon
|
156
|
+
extend Kernel.const_get("StraightServer::Addon::#{addon['module']}")
|
157
|
+
end if addons
|
158
|
+
end
|
159
|
+
|
160
|
+
# Finds orders that have statuses < 2 and starts querying the blockchain
|
161
|
+
# for them (unless they are also expired). This is for cases when the server was shut down,
|
162
|
+
# but some orders statuses are not resolved.
|
163
|
+
def resume_tracking_active_orders!
|
164
|
+
StraightServer::Order.where('status < 2').each do |order|
|
165
|
+
|
166
|
+
# Order is expired, but status is < 2! Suspcicious, probably
|
167
|
+
# an unclean shutdown of the server. Let's check and update the status manually once.
|
168
|
+
if order.time_left_before_expiration < 1
|
169
|
+
StraightServer.logger.info "Order #{order.id} seems to be expired, but status remains #{order.status}. Will check for status update manually."
|
170
|
+
order.status(reload: true)
|
171
|
+
|
172
|
+
# if we still see no transactions to that address,
|
173
|
+
# consider the order truly expired and update the status accordingly
|
174
|
+
order.status = StraightServer::Order::STATUSES[:expired] if order.status < 2
|
175
|
+
order.save
|
176
|
+
StraightServer.logger.info "Order #{order.id} status updated, new status is #{order.status}"
|
177
|
+
|
178
|
+
# Order is NOT expired and status is < 2. Let's keep tracking it.
|
179
|
+
else
|
180
|
+
StraightServer.logger.info "Resuming tracking of order #{order.id}, current status is #{order.status}, time before expiration: #{order.time_left_before_expiration} seconds."
|
181
|
+
StraightServer::Thread.new do
|
182
|
+
order.start_periodic_status_check
|
183
|
+
end
|
184
|
+
end
|
74
185
|
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Loads redis gem and sets up key prefixes for order counters
|
189
|
+
# for the current straight environment.
|
190
|
+
def setup_redis_connection
|
191
|
+
require 'redis'
|
192
|
+
Config.redis = Config.redis.keys_to_sym
|
193
|
+
Config.redis[:connection] = Redis.new(
|
194
|
+
host: Config.redis[:host],
|
195
|
+
port: Config.redis[:port],
|
196
|
+
db: Config.redis[:db],
|
197
|
+
password: Config.redis[:password]
|
198
|
+
)
|
199
|
+
Config.redis[:prefix] ||= "StraightServer:#{Config.environment}"
|
200
|
+
end
|
75
201
|
|
76
202
|
end
|
77
203
|
|
@@ -6,7 +6,7 @@ module StraightServer
|
|
6
6
|
|
7
7
|
n.times { puts "\n" }
|
8
8
|
|
9
|
-
File.open(StraightServer::Initializer::
|
9
|
+
File.open(StraightServer::Initializer::ConfigDir.path + '/' + Config.logmaster['file'], 'a') do |f|
|
10
10
|
n.times do
|
11
11
|
f.puts "\n"
|
12
12
|
end
|