straight-server 0.2.3 → 1.0.0

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.
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] = {}