straight-server 0.2.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -2
  3. data/Gemfile +21 -16
  4. data/Gemfile.lock +44 -30
  5. data/Gemfile.travis +15 -16
  6. data/README.md +66 -47
  7. data/VERSION +1 -1
  8. data/db/migrations/011_add_callback_data_to_orders.rb +1 -1
  9. data/db/migrations/012_add_address_provider.rb +11 -0
  10. data/db/migrations/013_add_address_derivation_scheme.rb +11 -0
  11. data/db/migrations/014_pubkey_null_address_provider_not_null.rb +8 -0
  12. data/db/migrations/015_add_amount_paid_to_orders.rb +11 -0
  13. data/db/migrations/016_add_new_params_to_orders.rb +13 -0
  14. data/db/migrations/017_add_test_mode_to_gateways.rb +11 -0
  15. data/db/migrations/018_add_test_keychain_id_to_gateways.rb +11 -0
  16. data/db/migrations/019_add_test_pubkey_to_gateways.rb +11 -0
  17. data/db/migrations/020_add_test_mode_to_orders.rb +11 -0
  18. data/db/schema.rb +11 -1
  19. data/lib/straight-server.rb +11 -9
  20. data/lib/straight-server/config.rb +28 -18
  21. data/lib/straight-server/gateway.rb +167 -87
  22. data/lib/straight-server/initializer.rb +13 -7
  23. data/lib/straight-server/order.rb +39 -17
  24. data/lib/straight-server/orders_controller.rb +71 -21
  25. data/lib/straight-server/random_string.rb +3 -13
  26. data/lib/straight-server/server.rb +3 -4
  27. data/lib/straight-server/signature_validator.rb +69 -0
  28. data/lib/straight-server/thread.rb +19 -4
  29. data/lib/straight-server/throttler.rb +7 -13
  30. data/lib/tasks/db.rake +1 -1
  31. data/spec/.straight/config.yml +8 -3
  32. data/spec/.straight/default_test_last_keychain_id +1 -0
  33. data/spec/factories.rb +2 -1
  34. data/spec/lib/gateway_spec.rb +222 -94
  35. data/spec/lib/initializer_spec.rb +1 -1
  36. data/spec/lib/order_spec.rb +26 -7
  37. data/spec/lib/orders_controller_spec.rb +65 -6
  38. data/spec/lib/signature_validator_spec.rb +72 -0
  39. data/spec/lib/thread_spec.rb +16 -0
  40. data/spec/lib/throttle_spec.rb +2 -2
  41. data/spec/spec_helper.rb +17 -22
  42. data/straight-server.gemspec +31 -12
  43. data/templates/config.yml +19 -10
  44. metadata +52 -11
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.3
1
+ 1.0.0
@@ -5,7 +5,7 @@ Sequel.migration do
5
5
  end
6
6
 
7
7
  down do
8
- remove_column :orders, :callback_data
8
+ drop_column :orders, :callback_data
9
9
  end
10
10
 
11
11
  end
@@ -0,0 +1,11 @@
1
+ Sequel.migration do
2
+
3
+ up do
4
+ add_column :gateways, :address_provider, String, default: "Bip32"
5
+ end
6
+
7
+ down do
8
+ drop_column :gateways, :address_provider
9
+ end
10
+
11
+ end
@@ -0,0 +1,11 @@
1
+ Sequel.migration do
2
+
3
+ up do
4
+ add_column :gateways, :address_derivation_scheme, String
5
+ end
6
+
7
+ down do
8
+ drop_column :gateways, :address_derivation_scheme
9
+ end
10
+
11
+ end
@@ -0,0 +1,8 @@
1
+ Sequel.migration do
2
+ change do
3
+ alter_table :gateways do
4
+ set_column_allow_null :pubkey
5
+ set_column_not_null :address_provider
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,11 @@
1
+ Sequel.migration do
2
+
3
+ up do
4
+ add_column :orders, :amount_paid, Bignum
5
+ end
6
+
7
+ down do
8
+ drop_column :orders, :amount_paid
9
+ end
10
+
11
+ end
@@ -0,0 +1,13 @@
1
+ Sequel.migration do
2
+
3
+ up do
4
+ add_column :orders, :callback_url, String
5
+ add_column :orders, :title, String
6
+ end
7
+
8
+ down do
9
+ drop_column :orders, :callback_url
10
+ drop_column :orders, :title
11
+ end
12
+
13
+ end
@@ -0,0 +1,11 @@
1
+ Sequel.migration do
2
+
3
+ up do
4
+ add_column :gateways, :test_mode, TrueClass, default: false
5
+ end
6
+
7
+ down do
8
+ drop_column :gateways, :test_mode
9
+ end
10
+
11
+ end
@@ -0,0 +1,11 @@
1
+ Sequel.migration do
2
+
3
+ up do
4
+ add_column :gateways, :test_last_keychain_id, Integer, default: 0, null: false
5
+ end
6
+
7
+ down do
8
+ drop_column :gateways, :test_last_keychain_id
9
+ end
10
+
11
+ end
@@ -0,0 +1,11 @@
1
+ Sequel.migration do
2
+
3
+ up do
4
+ add_column :gateways, :test_pubkey, String
5
+ end
6
+
7
+ down do
8
+ drop_column :gateways, :test_pubkey
9
+ end
10
+
11
+ end
@@ -0,0 +1,11 @@
1
+ Sequel.migration do
2
+
3
+ up do
4
+ add_column :orders, :test_mode, TrueClass, default: false
5
+ end
6
+
7
+ down do
8
+ drop_column :orders, :test_mode
9
+ end
10
+
11
+ end
@@ -4,7 +4,7 @@ Sequel.migration do
4
4
  primary_key :id
5
5
  Integer :confirmations_required, :default=>0, :null=>false
6
6
  Integer :last_keychain_id, :default=>0, :null=>false
7
- String :pubkey, :size=>255, :null=>false
7
+ String :pubkey, :size=>255
8
8
  String :order_class, :size=>255, :null=>false
9
9
  String :secret, :size=>255, :null=>false
10
10
  String :name, :size=>255, :null=>false
@@ -19,6 +19,11 @@ Sequel.migration do
19
19
  TrueClass :active, :default=>true
20
20
  String :order_counters, :size=>255
21
21
  String :hashed_id, :size=>255
22
+ String :address_provider, :default=>"Bip32", :size=>255, :null=>false
23
+ String :address_derivation_scheme, :size=>255
24
+ TrueClass :test_mode, :default=>false
25
+ Integer :test_last_keychain_id, :default=>0, :null=>false
26
+ String :test_pubkey, :size=>255
22
27
 
23
28
  index [:hashed_id]
24
29
  index [:id], :unique=>true
@@ -41,6 +46,11 @@ Sequel.migration do
41
46
  String :payment_id, :size=>255
42
47
  String :description, :size=>255
43
48
  Integer :reused, :default=>0
49
+ String :callback_data, :size=>255
50
+ String :amount_paid
51
+ String :callback_url, :size=>255
52
+ String :title, :size=>255
53
+ TrueClass :test_mode, :default=>false
44
54
 
45
55
  index [:address]
46
56
  index [:id], :unique=>true
@@ -6,23 +6,25 @@ require 'logmaster'
6
6
  require 'openssl'
7
7
  require 'base64'
8
8
  require 'net/http'
9
+ require 'redis'
9
10
  require 'faye/websocket'
10
11
  Sequel.extension :migration
11
12
 
12
-
13
- require_relative 'straight-server/utils/hash_string_to_sym_keys'
14
- require_relative 'straight-server/random_string'
15
- require_relative 'straight-server/config'
16
- require_relative 'straight-server/initializer'
17
- require_relative 'straight-server/thread'
18
- require_relative 'straight-server/orders_controller'
19
-
20
13
  module StraightServer
21
14
 
22
15
  VERSION = File.read(File.expand_path('../', File.dirname(__FILE__)) + '/VERSION')
23
16
 
17
+ StraightServerError = Class.new(StandardError)
18
+
24
19
  class << self
25
- attr_accessor :db_connection, :logger
20
+ attr_accessor :db_connection, :redis_connection, :logger
26
21
  end
27
22
 
28
23
  end
24
+
25
+ require_relative 'straight-server/utils/hash_string_to_sym_keys'
26
+ require_relative 'straight-server/random_string'
27
+ require_relative 'straight-server/config'
28
+ require_relative 'straight-server/initializer'
29
+ require_relative 'straight-server/thread'
30
+ require_relative 'straight-server/orders_controller'
@@ -1,24 +1,34 @@
1
+ require 'ostruct'
2
+
1
3
  module StraightServer
2
4
 
3
- class Config
5
+ # :db
6
+ # :gateways_source
7
+ # :gateways
8
+ # :logmaster
9
+ # :server_secret
10
+ # :count_orders
11
+ # :environment
12
+ # :redis
13
+ # :check_order_status_in_db_first
14
+ # :port
15
+ # :blockchain_adapters
16
+ # :expiration_overtime
17
+ # :reuse_address_orders_threshold
18
+ # :throttle
4
19
 
5
- class << self
6
- attr_accessor :db,
7
- :gateways_source,
8
- :gateways,
9
- :logmaster,
10
- :server_secret,
11
- :count_orders,
12
- :environment,
13
- :redis,
14
- :check_order_status_in_db_first,
15
- :port,
16
- :blockchain_adapters,
17
- :expiration_overtime,
18
- :reuse_address_orders_threshold,
19
- :throttle
20
+ class << (Config = OpenStruct.new)
21
+ def [](key_chain)
22
+ key_chain = key_chain.to_s.split('.')
23
+ config = self.public_send(key_chain.shift)
24
+ key_chain.each do |key|
25
+ if config.kind_of?(Hash)
26
+ config = config[key] || config[key.to_sym]
27
+ else
28
+ return
29
+ end
30
+ end
31
+ config
20
32
  end
21
-
22
33
  end
23
-
24
34
  end
@@ -1,36 +1,30 @@
1
+ require 'cgi'
2
+
1
3
  module StraightServer
2
4
 
3
5
  # This module contains common features of Gateway, later to be included
4
6
  # in one of the classes below.
5
7
  module GatewayModule
6
8
 
7
- # Temporary fix for straight server benchmarking
8
- @@redis = StraightServer::Config.redis[:connection] if StraightServer::Config.redis
9
9
  @@websockets = {}
10
-
11
- def fetch_transactions_for(address)
12
- try_adapters(@blockchain_adapters, type: 'blockchain') { |b| b.fetch_transactions_for(address) }
13
- end
14
10
 
15
- class InvalidSignature < Exception; end
16
- class InvalidOrderId < Exception; end
17
- class CallbackUrlBadResponse < Exception; end
18
- class WebsocketExists < Exception; end
19
- class WebsocketForCompletedOrder < Exception; end
20
- class GatewayInactive < Exception; end
21
- class NoBlockchainAdapters < Exception
11
+ class CallbackUrlBadResponse < StraightServerError; end
12
+ class WebsocketExists < StraightServerError; end
13
+ class WebsocketForCompletedOrder < StraightServerError; end
14
+ class GatewayInactive < StraightServerError; end
15
+ class NoBlockchainAdapters < StraightServerError
22
16
  def message
23
17
  "No blockchain adapters were found! StraightServer cannot query the blockchain.\n" +
24
18
  "Check your ~/.straight/config.yml file and make sure valid blockchain adapters\n" +
25
19
  "are present."
26
20
  end
27
21
  end
28
- class NoWebsocketsForNewGateway < Exception
22
+ class NoWebsocketsForNewGateway < StraightServerError
29
23
  def message
30
24
  "You're trying to get access to websockets on a Gateway that hasn't been saved yet"
31
25
  end
32
26
  end
33
- class OrderCountersDisabled < Exception
27
+ class OrderCountersDisabled < StraightServerError
34
28
  def message
35
29
  "Please enable order counting in config file! You can do is using the following option:\n\n" +
36
30
  " count_orders: true\n\n" +
@@ -41,6 +35,18 @@ module StraightServer
41
35
  " db: null\n"
42
36
  end
43
37
  end
38
+ class NoPubkey < StraightServerError
39
+ def message
40
+ "No public key were found! Gateway can't work without it.\n" +
41
+ "Please provide it in config file or DB."
42
+ end
43
+ end
44
+ class NoTestPubkey < StraightServerError
45
+ def message
46
+ "No test public key were found! Gateway can't work in test mode without it.\n" +
47
+ "Please provide it in config file or DB."
48
+ end
49
+ end
44
50
 
45
51
  CALLBACK_URL_ATTEMPT_TIMEFRAME = 3600 # seconds
46
52
 
@@ -56,7 +62,7 @@ module StraightServer
56
62
  self.exchange_rate_adapter_names.each do |adapter|
57
63
  begin
58
64
  @exchange_rate_adapters << Straight::ExchangeRate.const_get("#{adapter}Adapter").instance
59
- rescue NameError => e
65
+ rescue NameError => e
60
66
  puts "WARNING: No exchange rate adapter with the name #{adapter} was found!"
61
67
  end
62
68
  end
@@ -67,18 +73,15 @@ module StraightServer
67
73
  @blockchain_adapters = []
68
74
  StraightServer::Config.blockchain_adapters.each do |a|
69
75
 
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
76
+ adapter = Straight::Blockchain.const_get("#{a}Adapter")
77
+ next unless adapter
78
+ begin
79
+ main_url = StraightServer::Config.__send__("#{a.downcase}_url") rescue next
80
+ test_url = StraightServer::Config.__send__("#{a.downcase}_test_url") rescue nil
81
+ @blockchain_adapters << adapter.mainnet_adapter(main_url: main_url, test_url: test_url)
82
+ rescue ArgumentError
83
+ @blockchain_adapters << adapter.mainnet_adapter
79
84
  end
80
-
81
- @blockchain_adapters << adapter.mainnet_adapter if adapter
82
85
  end
83
86
  raise NoBlockchainAdapters if @blockchain_adapters.empty?
84
87
  end
@@ -89,9 +92,11 @@ module StraightServer
89
92
  @order_callbacks = [
90
93
  lambda do |order|
91
94
  StraightServer::Thread.new do
92
- send_callback_http_request order
93
95
  send_order_to_websocket_client order
94
96
  end
97
+ StraightServer::Thread.new do
98
+ send_callback_http_request order
99
+ end
95
100
  end
96
101
  ]
97
102
  end
@@ -99,10 +104,20 @@ module StraightServer
99
104
  def initialize_status_check_schedule
100
105
  @status_check_schedule = Straight::GatewayModule::DEFAULT_STATUS_CHECK_SCHEDULE
101
106
  end
107
+
108
+ def initialize_network
109
+ BTC::Network.default = test_mode ? BTC::Network.testnet : BTC::Network.mainnet
110
+ end
102
111
  #
103
112
  ############# END OF Initializers methods ##################################################
104
-
105
-
113
+
114
+ def fetch_transactions_for(address)
115
+ super
116
+ rescue Straight::Blockchain::Adapter::BitcoinAddressInvalid => e
117
+ StraightServer.logger.warn "Address seems to be invalid, ignoring it. #{e.message}"
118
+ return []
119
+ end
120
+
106
121
  # Creates a new order and saves into the DB. Checks if the MD5 hash
107
122
  # is correct first.
108
123
  def create_order(attrs={})
@@ -110,43 +125,53 @@ module StraightServer
110
125
  raise GatewayInactive unless self.active
111
126
 
112
127
  StraightServer.logger.info "Creating new order with attrs: #{attrs}"
113
- signature = attrs.delete(:signature)
114
- if !check_signature || sign_with_secret(attrs[:keychain_id]) == signature
115
- raise InvalidOrderId if check_signature && (attrs[:keychain_id].nil? || attrs[:keychain_id].to_i <= 0)
116
-
117
- # If we decide to reuse the order, we simply need to supply the
118
- # keychain_id that was used in the order we're reusing.
119
- # The address will be generated correctly.
120
- if reused_order = find_reusable_order
121
- attrs[:keychain_id] = reused_order.keychain_id
122
- end
123
128
 
124
- order = order_for_keychain_id(
125
- amount: attrs[:amount],
126
- keychain_id: attrs[:keychain_id] || self.last_keychain_id+1,
127
- currency: attrs[:currency],
128
- btc_denomination: attrs[:btc_denomination]
129
- )
130
- order.id = attrs[:id].to_i if attrs[:id]
131
- order.data = attrs[:data] if attrs[:data]
132
- order.callback_data = attrs[:callback_data] if attrs[:callback_data]
133
- order.gateway = self
134
- order.reused = reused_order.reused + 1 if reused_order
135
- order.save
136
-
137
- self.update_last_keychain_id(attrs[:keychain_id]) unless order.reused > 0
138
- self.save
139
- StraightServer.logger.info "Order #{order.id} created: #{order.to_h}"
140
- order
141
- else
142
- StraightServer.logger.warn "Invalid signature, cannot create an order for gateway (#{id})"
143
- raise InvalidSignature
129
+ # If we decide to reuse the order, we simply need to supply the
130
+ # keychain_id that was used in the order we're reusing.
131
+ # The address will be generated correctly.
132
+ if reused_order = find_reusable_order
133
+ attrs[:keychain_id] = reused_order.keychain_id
134
+
144
135
  end
136
+
137
+ attrs[:keychain_id] = nil if attrs[:keychain_id] == ''
138
+
139
+
140
+ order = new_order(
141
+ amount: (attrs[:amount] && attrs[:amount].to_f),
142
+ keychain_id: attrs[:keychain_id] || get_next_last_keychain_id,
143
+ currency: attrs[:currency],
144
+ btc_denomination: attrs[:btc_denomination]
145
+ )
146
+
147
+ order.id = attrs[:id].to_i if attrs[:id]
148
+ order.data = attrs[:data] if attrs[:data]
149
+ order.callback_data = attrs[:callback_data] if attrs[:callback_data]
150
+ order.title = attrs[:title] if attrs[:title]
151
+ order.callback_url = attrs[:callback_url] if attrs[:callback_url]
152
+ order.gateway = self
153
+ order.test_mode = test_mode
154
+ order.description = attrs[:description]
155
+ order.reused = reused_order.reused + 1 if reused_order
156
+ order.save
157
+
158
+ self.update_last_keychain_id(attrs[:keychain_id]) unless order.reused > 0
159
+ self.save
160
+ StraightServer.logger.info "Order #{order.id} created: #{order.to_h}"
161
+ order
162
+ end
163
+
164
+ def get_next_last_keychain_id
165
+ self.test_mode ? self.test_last_keychain_id + 1 : self.last_keychain_id + 1
145
166
  end
146
167
 
168
+ # TODO: make it pretty
147
169
  def update_last_keychain_id(new_value=nil)
148
- #new_value = nil if new_value && new_value.empty?
149
- new_value ? self.last_keychain_id = new_value : self.last_keychain_id += 1
170
+ if self.test_mode
171
+ new_value ? self.test_last_keychain_id = new_value : self.test_last_keychain_id += 1
172
+ else
173
+ new_value ? self.last_keychain_id = new_value : self.last_keychain_id += 1
174
+ end
150
175
  end
151
176
 
152
177
  def add_websocket_for_order(ws, order)
@@ -198,18 +223,19 @@ module StraightServer
198
223
  paid: get_order_counter(:paid),
199
224
  underpaid: get_order_counter(:underpaid),
200
225
  overpaid: get_order_counter(:overpaid),
201
- expired: get_order_counter(:expired)
226
+ expired: get_order_counter(:expired),
227
+ canceled: get_order_counter(:canceled),
202
228
  }
203
229
  end
204
230
 
205
231
  def get_order_counter(counter_name)
206
232
  raise OrderCountersDisabled unless StraightServer::Config.count_orders
207
- @@redis.get("#{StraightServer::Config.redis[:prefix]}:gateway_#{id}:#{counter_name}_orders_counter").to_i || 0
233
+ StraightServer.redis_connection.get("#{StraightServer::Config.redis[:prefix]}:gateway_#{id}:#{counter_name}_orders_counter").to_i || 0
208
234
  end
209
235
 
210
236
  def increment_order_counter!(counter_name, by=1)
211
237
  raise OrderCountersDisabled unless StraightServer::Config.count_orders
212
- @@redis.incrby("#{StraightServer::Config.redis[:prefix]}:gateway_#{id}:#{counter_name}_orders_counter", by)
238
+ StraightServer.redis_connection.incrby("#{StraightServer::Config.redis[:prefix]}:gateway_#{id}:#{counter_name}_orders_counter", by)
213
239
  end
214
240
 
215
241
  # If we have more than Config.reuse_address_orders_threshold i a row for this gateway,
@@ -234,33 +260,38 @@ module StraightServer
234
260
  private
235
261
 
236
262
  # Tries to send a callback HTTP request to the resource specified
237
- # in the #callback_url. If it fails for any reason, it keeps trying for an hour (3600 seconds)
263
+ # in the #callback_url. Use #callback_url given to Order if it exist, otherwise default.
264
+ # If it fails for any reason, it keeps trying for an hour (3600 seconds)
238
265
  # making 10 http requests, each delayed by twice the time the previous one was delayed.
239
266
  # This method is supposed to be running in a separate thread.
240
267
  def send_callback_http_request(order, delay: 5)
241
- return if callback_url.nil?
242
-
243
- StraightServer.logger.info "Attempting to send request to the callback url for order #{order.id} to #{callback_url}..."
268
+ url = order.callback_url || self.callback_url
269
+ return if url.to_s.empty?
244
270
 
245
271
  # Composing the request uri here
246
- signature = self.check_signature ? "&signature=#{sign_with_secret(order.id)}" : ''
247
- callback_data = order.callback_data ? "&callback_data=#{order.callback_data}" : ''
248
- uri = URI.parse(callback_url + '?' + order.to_http_params + signature + callback_data)
272
+ callback_data = order.callback_data ? "&callback_data=#{CGI.escape(order.callback_data)}" : ''
273
+ uri = URI.parse("#{url}#{url.include?('?') ? '&' : '?'}#{order.to_http_params}#{callback_data}")
274
+
275
+ StraightServer.logger.info "Attempting callback for order #{order.id}: #{uri.to_s}"
249
276
 
250
277
  begin
251
- response = Net::HTTP.get_response(uri)
278
+ request = Net::HTTP::Get.new(uri.request_uri)
279
+ request.add_field 'X-Signature', SignatureValidator.signature(method: 'GET', request_uri: uri.request_uri, secret: secret, nonce: nil, body: nil)
280
+ response = Net::HTTP.new(uri.host, uri.port).start do |http|
281
+ http.request request
282
+ end
252
283
  order.callback_response = { code: response.code, body: response.body }
253
284
  order.save
254
285
  raise CallbackUrlBadResponse unless response.code.to_i == 200
255
- rescue Exception => e
286
+ rescue => ex
256
287
  if delay < CALLBACK_URL_ATTEMPT_TIMEFRAME
257
288
  sleep(delay)
258
289
  send_callback_http_request(order, delay: delay*2)
259
290
  else
260
- StraightServer.logger.warn "Callback request for order #{order.id} failed, see order's #callback_response field for details"
291
+ StraightServer.logger.warn "Callback request for order #{order.id} failed with #{ex.inspect}, see order's #callback_response field for details"
261
292
  end
262
293
  end
263
-
294
+
264
295
  StraightServer.logger.info "Callback request for order #{order.id} performed successfully"
265
296
  end
266
297
 
@@ -287,7 +318,7 @@ module StraightServer
287
318
  # Return the row of expired orders - which is not enough to trigger a reuse
288
319
  # (the triger is in the #find_reusable_order method, which calls this one).
289
320
  def find_expired_orders_row
290
-
321
+
291
322
  orders = []
292
323
  row = nil
293
324
  offset = 0
@@ -298,7 +329,7 @@ module StraightServer
298
329
  row.reject! do |o|
299
330
  reject = false
300
331
  row.each do |o2|
301
- reject = true if o.keychain_id == o2.keychain_id && o.reused < o2.reused
332
+ reject = true if o.keychain_id == o2.keychain_id && o.reused < o2.reused
302
333
  end
303
334
  reject
304
335
  end
@@ -330,10 +361,8 @@ module StraightServer
330
361
  include GatewayModule
331
362
  plugin :timestamps, create: :created_at, update: :updated_at
332
363
  plugin :serialization, :marshal, :exchange_rate_adapter_names
333
- plugin :serialization, :marshal
334
364
  plugin :after_initialize
335
365
 
336
-
337
366
  def self.find_by_hashed_id(s)
338
367
  self.where(hashed_id: s).first
339
368
  end
@@ -347,6 +376,8 @@ module StraightServer
347
376
  def before_create
348
377
  super
349
378
  encrypt_secret
379
+ self.test_mode ||= false
380
+ self.test_last_keychain_id ||= 0
350
381
  end
351
382
 
352
383
  def before_update
@@ -367,8 +398,15 @@ module StraightServer
367
398
  initialize_exchange_rate_adapters
368
399
  initialize_blockchain_adapters
369
400
  initialize_status_check_schedule
401
+ initialize_network
370
402
  end
371
-
403
+
404
+ def validate
405
+ super
406
+ errors.add(:pubkey, "Please provide public key") if pubkey_missing?
407
+ errors.add(:test_pubkey, "Please provide test public key if you activate test mode") if test_pubkey_missing?
408
+ end
409
+
372
410
  # We cannot allow to store gateway secret in a DB plaintext, this would be completetly unsecure.
373
411
  # Althougth we use symmetrical encryption here and store the encryption key in the
374
412
  # server's in a special file (~/.straight/server_secret), which in turn can also be stolen,
@@ -393,9 +431,9 @@ module StraightServer
393
431
  raise "cipher.iv cannot be nil" unless iv
394
432
 
395
433
  encrypted = cipher.update(self[:secret]) << cipher.final()
396
- base64_encrypted = Base64.strict_encode64(encrypted).encode('utf-8')
434
+ base64_encrypted = Base64.strict_encode64(encrypted).encode('utf-8')
397
435
  result = "#{iv}:#{base64_encrypted}"
398
-
436
+
399
437
  # Check whether we can decrypt. It should not be possible to encrypt the
400
438
  # gateway secret unless we are sure we can decrypt it.
401
439
  if decrypt_secret(result) == self[:secret]
@@ -405,6 +443,24 @@ module StraightServer
405
443
  end
406
444
  end
407
445
 
446
+ def address_provider_type
447
+ self[:address_provider] ? self[:address_provider].to_sym : :Bip32
448
+ end
449
+
450
+ def address_provider
451
+ Kernel.const_get("Straight::AddressProvider::#{address_provider_type}").new(self)
452
+ end
453
+
454
+ def disable_test_mode!
455
+ self[:test_mode] = false
456
+ save(columns: 'test_mode')
457
+ end
458
+
459
+ def enable_test_mode!
460
+ self[:test_mode] = true
461
+ save(columns: 'test_mode')
462
+ end
463
+
408
464
  private
409
465
 
410
466
  def decrypt_secret(encrypted_field=self[:secret])
@@ -433,7 +489,7 @@ module StraightServer
433
489
  attr_accessor :secret
434
490
 
435
491
  # This is used to generate the next address to accept payments
436
- attr_accessor :last_keychain_id
492
+ attr_accessor :last_keychain_id, :test_last_keychain_id
437
493
 
438
494
  # If set to false, doesn't require an unique id of the order along with
439
495
  # the signed md5 hash of that id + secret to be passed into the #create_order method.
@@ -451,7 +507,7 @@ module StraightServer
451
507
 
452
508
  attr_accessor :exchange_rate_adapter_names
453
509
  attr_accessor :orders_expiration_period
454
-
510
+
455
511
  # This affects whether it is possible to create a new order with the gateway.
456
512
  # If it's set to false, then it won't be possible to create a new order, but
457
513
  # it will keep checking on the existing ones.
@@ -466,6 +522,12 @@ module StraightServer
466
522
  initialize_exchange_rate_adapters
467
523
  initialize_blockchain_adapters
468
524
  initialize_status_check_schedule
525
+ initialize_network
526
+ end
527
+
528
+ def validate_config
529
+ raise NoPubkey if pubkey_missing?
530
+ raise NoTestPubkey if test_pubkey_missing?
469
531
  end
470
532
 
471
533
  # Because this is a config based gateway, we only save last_keychain_id
@@ -478,7 +540,7 @@ module StraightServer
478
540
  # If the file doesn't exist, we create it. Later, whenever an attribute is updated,
479
541
  # we save it to the file.
480
542
  def load_last_keychain_id!
481
- @last_keychain_id_file ||= StraightServer::Initializer::ConfigDir.path + "/#{name}_last_keychain_id"
543
+ @last_keychain_id_file ||= build_keychain_path
482
544
  if File.exists?(@last_keychain_id_file)
483
545
  self.last_keychain_id = File.read(@last_keychain_id_file).to_i
484
546
  else
@@ -488,10 +550,23 @@ module StraightServer
488
550
  end
489
551
 
490
552
  def save_last_keychain_id!
491
- @last_keychain_id_file ||= StraightServer::Initializer::ConfigDir.path + "/#{name}_last_keychain_id"
553
+ @last_keychain_id_file ||= build_keychain_path
492
554
  File.open(@last_keychain_id_file, 'w') {|f| f.write(last_keychain_id) }
493
555
  end
494
556
 
557
+ def build_keychain_path
558
+ filename = self.test_mode ? "/#{name}_test_last_keychain_id" : "/#{name}_last_keychain_id"
559
+ StraightServer::Initializer::ConfigDir.path + filename
560
+ end
561
+
562
+ def address_provider_type
563
+ @address_provider ? @address_provider.to_sym : :Bip32
564
+ end
565
+
566
+ def address_provider
567
+ Kernel.const_get("Straight::AddressProvider::#{address_provider_type}").new(self)
568
+ end
569
+
495
570
  # This method is a replacement for the Sequel's model one used in DB version of the gateway
496
571
  # and it finds gateways using the index of @@gateways Array.
497
572
  def self.find_by_id(id)
@@ -509,6 +584,7 @@ module StraightServer
509
584
  i += 1
510
585
  gateway = self.new
511
586
  gateway.pubkey = attrs['pubkey']
587
+ gateway.test_pubkey = attrs['test_pubkey']
512
588
  gateway.confirmations_required = attrs['confirmations_required'].to_i
513
589
  gateway.order_class = attrs['order_class']
514
590
  gateway.secret = attrs['secret']
@@ -517,9 +593,13 @@ module StraightServer
517
593
  gateway.default_currency = attrs['default_currency']
518
594
  gateway.orders_expiration_period = attrs['orders_expiration_period']
519
595
  gateway.active = attrs['active']
596
+ gateway.address_provider = attrs['address_provider'] || "Bip32"
597
+ gateway.address_derivation_scheme = attrs['address_derivation_scheme']
598
+ gateway.test_mode = attrs['test_mode'] || false
520
599
  gateway.name = name
521
600
  gateway.id = i
522
601
  gateway.exchange_rate_adapter_names = attrs['exchange_rate_adapters']
602
+ gateway.validate_config
523
603
  gateway.initialize_exchange_rate_adapters
524
604
  gateway.load_last_keychain_id!
525
605
  @@websockets[i] = {}