radiator 0.3.0dev3 → 0.3.0dev4

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