radiator 0.3.0dev3 → 0.3.0dev4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 768d130d67520d82f31a14a1ce99236e5b17688b
4
- data.tar.gz: 6da4c36f9d54a3de922ac26ec4ed8f70aee0def1
3
+ metadata.gz: 4b709aac2407041d491d0d08abb766ce404dc0cf
4
+ data.tar.gz: d007d6f2d7d20b096450f05c196790da6374b76e
5
5
  SHA512:
6
- metadata.gz: 2c8507502286b7d4f07c6705bbbd8fdd0bfc04d5a6187747b1573a9714bcdf58ecf5dcb12e615fa2eb3218ef91a1b934470c70c0da742f0fdb6fdb448697a12f
7
- data.tar.gz: 86d7090a11dbd508db65305bccf71d3c6c1533a962cbdc66c8b4c9b42105470341274d81010fd8aed902e43085fbae3fa7f06fbd9561cfd6800c9f986a39975c
6
+ metadata.gz: 073339bb35cbffff34c4adedfef58534b0827aac9757f903f94f7a2ae56adb27ef8468dc85271895a725a70eb425015faa3609424fc266d88581e81f0acd023e
7
+ data.tar.gz: a7cae3da19c410b1fceb01b717de6b4199dea2f7ebbd8018b7f1ef21c07e6da58439c0016508d60c7d1b9c6f8bd9f488b66179519018f7c40afc50a6e2a5f581
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- radiator (0.3.0dev3)
4
+ radiator (0.3.0dev4)
5
5
  bitcoin-ruby (= 0.0.11)
6
6
  ffi (= 1.9.18)
7
7
  hashie (~> 3.5, >= 3.5.5)
data/lib/radiator/api.rb CHANGED
@@ -161,6 +161,7 @@ module Radiator
161
161
  # @option options [Array<String>] :failover_urls An array that contains one or more full nodes to fall back on. Default from DEFAULT_FAILOVER_URLS.
162
162
  # @option options [Logger] :logger An instance of `Logger` to send debug messages to.
163
163
  # @option options [Boolean] :recover_transactions_on_error Have Radiator try to recover transactions that are accepted but could not be confirmed due to an error like network timeout. Default: `true`
164
+ # @option options [Integer] :max_requests Maximum number of requests on a connection before it is considered expired and automatically closed.
164
165
  def initialize(options = {})
165
166
  @user = options[:user]
166
167
  @password = options[:password]
@@ -171,6 +172,7 @@ module Radiator
171
172
  @debug = !!options[:debug]
172
173
  @logger = options[:logger] || Radiator.logger
173
174
  @hashie_logger = options[:hashie_logger] || Logger.new(nil)
175
+ @max_requests = options[:max_requests] || 30
174
176
 
175
177
  unless @hashie_logger.respond_to? :warn
176
178
  @hashie_logger = Logger.new(@hashie_logger)
@@ -304,8 +306,11 @@ module Radiator
304
306
  # Stops the persistant http connections.
305
307
  #
306
308
  def shutdown
309
+ @http_id = nil
307
310
  @http.shutdown if !!@http && defined?(@http.shutdown)
308
311
  @http = nil
312
+ @api.shutdown if !!@api && @api != self
313
+ @api = nil
309
314
  end
310
315
 
311
316
  # @private
@@ -403,6 +408,9 @@ module Radiator
403
408
  @logger.warn "Socket Error (#{e.message}), retrying ..."
404
409
  rescue JSON::ParserError => e
405
410
  @logger.warn "JSON Parse Error (#{e.message}), retrying ..."
411
+ response = nil
412
+ rescue ApiError => e
413
+ @logger.warn "ApiError (#{e.message}), retrying ..."
406
414
  rescue => e
407
415
  @logger.warn "Unknown exception from request, retrying ..."
408
416
  ap e if defined? ap
@@ -444,13 +452,19 @@ module Radiator
444
452
  @uri ||= URI.parse(@url)
445
453
  end
446
454
 
455
+ def http_id
456
+ @http_id ||= "radiator-#{Radiator::VERSION}-#{api_name}-#{SecureRandom.uuid}"
457
+ end
458
+
447
459
  def http
448
- @http_id ||= "radiator-#{Radiator::VERSION}-#{self.class.name.downcase}"
449
- @http ||= Net::HTTP::Persistent.new(@http_id).tap do |http|
450
- http.retry_change_requests = true
451
- http.max_requests = 30
460
+ @http ||= Net::HTTP::Persistent.new(http_id).tap do |http|
461
+ idempotent = api_name != :network_broadcast_api
462
+ http.keep_alive = 30
452
463
  http.read_timeout = 10
453
464
  http.open_timeout = 10
465
+ http.idle_timeout = idempotent ? 10 : nil
466
+ http.max_requests = @max_requests
467
+ http.retry_change_requests = idempotent
454
468
  end
455
469
  end
456
470
 
@@ -488,7 +502,7 @@ module Radiator
488
502
  # but we also give up once the block time is before the `after` argument.
489
503
 
490
504
  api.get_blocks(block_range) do |block, block_num|
491
- raise "Race condition detected at: #{block_num}" if block.nil?
505
+ raise ApiError, "Race condition detected on remote node at: #{block_num}" if block.nil?
492
506
 
493
507
  timestamp = Time.parse(block.timestamp + 'Z')
494
508
  break if timestamp < after
@@ -496,9 +510,6 @@ module Radiator
496
510
  block.transactions.each_with_index do |tx, index|
497
511
  next unless ((tx['signatures'] || []) & signatures).any?
498
512
 
499
- puts "Found matching signatures in #{(Time.now.utc - now)} seconds: #{signatures}"
500
- ap tx
501
-
502
513
  return {
503
514
  id: rpc_id,
504
515
  result: {
@@ -510,8 +521,6 @@ module Radiator
510
521
  }
511
522
  end
512
523
  end
513
-
514
- puts "Took #{(Time.now.utc - now)} seconds to scan for signatures."
515
524
  end
516
525
 
517
526
  def reset_failover
@@ -38,33 +38,42 @@ module Radiator
38
38
  # If symbol are passed, then only that operation is returned. Expected
39
39
  # symbols are:
40
40
  #
41
- # transfer_to_vesting
42
- # withdraw_vesting
43
- # interest
44
- # transfer
45
- # liquidity_reward
46
- # author_reward
47
- # curation_reward
48
- # transfer_to_savings
49
- # transfer_from_savings
41
+ # account_create
42
+ # account_create_with_delegation
43
+ # account_update
44
+ # account_witness_proxy
45
+ # account_witness_vote
50
46
  # cancel_transfer_from_savings
51
- # escrow_transfer
47
+ # change_recovery_account
48
+ # claim_reward_balance
49
+ # comment
50
+ # comment_options
51
+ # convert
52
+ # custom
53
+ # custom_json
54
+ # decline_voting_rights
55
+ # delegate_vesting_shares
56
+ # delete_comment
52
57
  # escrow_approve
53
58
  # escrow_dispute
54
59
  # escrow_release
55
- # comment
56
- # limit_order_create
60
+ # escrow_transfer
61
+ # feed_publish
57
62
  # limit_order_cancel
58
- # fill_convert_request
59
- # fill_order
63
+ # limit_order_create
64
+ # limit_order_create2
65
+ # pow
66
+ # pow2
67
+ # recover_account
68
+ # request_account_recovery
69
+ # set_withdraw_vesting_route
70
+ # transfer
71
+ # transfer_from_savings
72
+ # transfer_to_savings
73
+ # transfer_to_vesting
60
74
  # vote
61
- # account_witness_vote
62
- # account_witness_proxy
63
- # account_create
64
- # account_update
75
+ # withdraw_vesting
65
76
  # witness_update
66
- # pow
67
- # custom
68
77
  #
69
78
  # For example, to stream only votes:
70
79
  #
@@ -73,30 +82,93 @@ module Radiator
73
82
  # puts vote.to_json
74
83
  # end
75
84
  #
85
+ # You can also stream virtual operations:
86
+ #
87
+ # stream = Radiator::Stream.new
88
+ # stream.operations(:author_reward) do |vop|
89
+ # puts "#{vop.author} got paid for #{vop.permlink}: #{[vop.sbd_payout, vop.steem_payout, vop.vesting_payout]}"
90
+ # end
91
+ #
92
+ # ... or multiple virtual operation types;
93
+ #
94
+ # stream = Radiator::Stream.new
95
+ # stream.operations([:produer_reward, :author_reward]) do |vop|
96
+ # puts vop
97
+ # end
98
+ #
99
+ # ... or all types, inluding virtual operation types;
100
+ #
101
+ # stream = Radiator::Stream.new
102
+ # stream.operations(nil, nil, :head, include_virtual: true) do |vop|
103
+ # puts vop
104
+ # end
105
+ #
106
+ # Expected virtual operation types:
107
+ #
108
+ # author_reward
109
+ # curation_reward
110
+ # fill_convert_request
111
+ # fill_order
112
+ # fill_vesting_withdraw
113
+ # interest
114
+ # shutdown_witness
115
+ #
76
116
  # @param type [symbol || Array<symbol>] the type(s) of operation, optional.
77
117
  # @param start starting block
78
118
  # @param mode we have the choice between
79
119
  # * :head the last block
80
120
  # * :irreversible the block that is confirmed by 2/3 of all block producers and is thus irreversible!
81
121
  # @param block the block to execute for each result, optional.
122
+ # @param options [Hash] additional options
123
+ # @option options [Boollean] :include_virtual Also stream virtual options. Setting this true will impact performance. Default: false.
82
124
  # @return [Hash]
83
- def operations(type = nil, start = nil, mode = :irreversible, &block)
84
- transactions(start, mode) do |transaction|
125
+ def operations(type = nil, start = nil, mode = :irreversible, options = {include_virtual: false}, &block)
126
+ type = [type].flatten.compact.map(&:to_sym)
127
+ include_virtual = !!options[:include_virtual]
128
+
129
+ if virtual_op_type?(type)
130
+ include_virtual = true
131
+ end
132
+
133
+ latest_block_number = -1
134
+
135
+ transactions(start, mode) do |transaction, trx_id, block_number|
136
+ virtual_ops_collected = latest_block_number == block_number
137
+ latest_block_number = block_number
138
+
85
139
  ops = transaction.operations.map do |t, op|
86
140
  t = t.to_sym
87
- if type == t
141
+ if type.size == 1 && type.first == t
88
142
  op
89
- elsif type.nil? || [type].flatten.include?(t)
143
+ elsif type.none? || type.include?(t)
90
144
  {t => op}
91
145
  end
92
146
  end.compact
93
147
 
148
+ if include_virtual && !virtual_ops_collected
149
+ api.get_ops_in_block(block_number, true) do |vops|
150
+ vops.each do |vtx|
151
+ next unless defined? vtx.op
152
+
153
+ t = vtx.op.first.to_sym
154
+ op = vtx.op.last
155
+ if type.size == 1 && type.first == t
156
+ ops << op
157
+ elsif type.none? || type.include?(t)
158
+ ops << {t => op}
159
+ end
160
+ end
161
+ end
162
+
163
+ virtual_ops_collected = true
164
+ end
165
+
94
166
  next if ops.none?
95
167
 
96
168
  return ops unless !!block
97
169
 
98
170
  ops.each do |op|
99
- yield op
171
+ yield op, trx_id, block_number
100
172
  end
101
173
  end
102
174
  end
@@ -115,7 +187,7 @@ module Radiator
115
187
  # @param block the block to execute for each result, optional.
116
188
  # @return [Hash]
117
189
  def transactions(start = nil, mode = :irreversible, &block)
118
- blocks(start, mode) do |b|
190
+ blocks(start, mode) do |b, block_number|
119
191
  next if (_transactions = b.transactions).nil?
120
192
  return _transactions unless !!block
121
193
 
@@ -124,7 +196,7 @@ module Radiator
124
196
  b['transaction_ids'][index]
125
197
  end
126
198
 
127
- yield transaction, trx_id
199
+ yield transaction, trx_id, block_number
128
200
  end
129
201
  end
130
202
  end
@@ -145,44 +217,72 @@ module Radiator
145
217
  # @return [Hash]
146
218
  def blocks(start = nil, mode = :irreversible, max_blocks_per_node = MAX_BLOCKS_PER_NODE, &block)
147
219
  counter = 0
148
-
149
- if start.nil?
150
- properties = api.get_dynamic_global_properties.result
151
- start = case mode.to_sym
152
- when :head then properties.head_block_number
153
- when :irreversible then properties.last_irreversible_block_num
154
- else; raise StreamError, '"mode" has to be "head" or "irreversible"'
155
- end
156
- end
220
+ latest_block_number = -1
221
+ @api_options[:max_requests] = [max_blocks_per_node * 2, @api_options[:max_requests].to_i].max
157
222
 
158
223
  loop do
159
- properties = api.get_dynamic_global_properties.result
160
-
161
- head_block = case mode.to_sym
162
- when :head then properties.head_block_number
163
- when :irreversible then properties.last_irreversible_block_num
164
- else; raise StreamError, '"mode" has to be "head" or "irreversible"'
165
- end
166
-
167
- [*(start..(head_block))].each do |n|
168
- if (counter += 1) > max_blocks_per_node
169
- shutdown
170
- counter = 0
224
+ catch :sequence do; begin
225
+ head_block = api.get_dynamic_global_properties do |properties|
226
+ if properties.head_block_number.nil?
227
+ # This can happen if a reverse proxy is acting up.
228
+ @logger.warn "Bad block sequence after height: #{latest_block_number}"
229
+ throw :sequence
230
+ end
231
+
232
+ case mode.to_sym
233
+ when :head then properties.head_block_number
234
+ when :irreversible then properties.last_irreversible_block_num
235
+ else; raise StreamError, '"mode" has to be "head" or "irreversible"'
236
+ end
237
+ end
238
+
239
+ if head_block == latest_block_number
240
+ # This can when there's a delay in block production.
241
+ sleep 0.5
242
+ throw :sequence
243
+ elsif head_block < latest_block_number
244
+ # This can happen if a reverse proxy is acting up.
245
+ @logger.warn "Invalid block sequence at height: #{head_block}"
246
+ sleep 0.5
247
+ throw :sequence
171
248
  end
249
+
250
+ start ||= head_block
251
+ range = (start..head_block)
252
+
253
+ if range.size > 400
254
+ # When the range is 400 blocks, the stream will be behind by about
255
+ # 20 minutes. Time to warn.
256
+ @logger.warn "Stream behind by #{range.size} blocks (about #{(range.size * 3) / 60.0} minutes)."
257
+ end
258
+
259
+ [*range].each do |n|
260
+ if (counter += 1) > max_blocks_per_node
261
+ shutdown
262
+ counter = 0
263
+ end
172
264
 
173
- response = api.send(:get_block, n)
174
- raise StreamError, JSON[response.error] if !!response.error
175
- result = response.result
176
-
177
- if !!block
178
- yield result, n
179
- else
180
- return result, n
265
+ api.get_block(n) do |current_block, error|
266
+ if !!error
267
+ @logger.warn "Node responded with: #{error.message || 'unknown error'}"
268
+ ap error
269
+ throw :sequence
270
+ end
271
+
272
+ latest_block_number = n
273
+ return current_block, n if block.nil?
274
+ yield current_block, n
275
+ end
276
+
277
+ start = head_block + 1
278
+ sleep 3 / range.size
181
279
  end
182
- end
183
-
184
- start = head_block + 1
185
- sleep 3
280
+ rescue StreamError; raise
281
+ rescue => e
282
+ @logger.warn "Unknown streaming error: #{e.inspect}, retrying ... "
283
+ ap e
284
+ redo
285
+ end; end
186
286
  end
187
287
  end
188
288
 
@@ -241,10 +341,6 @@ module Radiator
241
341
  end
242
342
  end
243
343
  private
244
- def api
245
- @api ||= Api.new(@api_options)
246
- end
247
-
248
344
  def method_missing(m, *args, &block)
249
345
  super unless respond_to_missing?(m)
250
346
 
@@ -292,5 +388,11 @@ module Radiator
292
388
  @timeout = INITIAL_TIMEOUT if @timeout > MAX_TIMEOUT
293
389
  @timeout
294
390
  end
391
+
392
+ def virtual_op_type?(type)
393
+ type = [type].flatten.compact.map(&:to_sym)
394
+
395
+ (Radiator::OperationTypes::TYPES.keys && type).any?
396
+ end
295
397
  end
296
398
  end
@@ -106,6 +106,11 @@ module Radiator
106
106
  # The expiration allows for transactions to expire if they are not
107
107
  # included into a block by that time. Always update it to the current
108
108
  # time + EXPIRE_IN_SECS.
109
+ #
110
+ # Note, as of #1215, expiration exactly 'now' will be rejected:
111
+ # https://github.com/steemit/steem/blob/57451b80d2cf480dcce9b399e48e56aa7af1d818/libraries/chain/database.cpp#L2870
112
+ # https://github.com/steemit/steem/issues/1215
113
+
109
114
  @expiration = Time.parse(@properties.time + 'Z') + EXPIRE_IN_SECS
110
115
 
111
116
  self
@@ -1,3 +1,3 @@
1
1
  module Radiator
2
- VERSION = '0.3.0dev3'
2
+ VERSION = '0.3.0dev4'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: radiator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0dev3
4
+ version: 0.3.0dev4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anthony Martin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-09-30 00:00:00.000000000 Z
11
+ date: 2017-10-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -410,7 +410,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
410
410
  version: 1.3.1
411
411
  requirements: []
412
412
  rubyforge_project:
413
- rubygems_version: 2.6.13
413
+ rubygems_version: 2.6.12
414
414
  signing_key:
415
415
  specification_version: 4
416
416
  summary: STEEM RPC Ruby Client