rubybear 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -0
  3. data/Gemfile.lock +77 -0
  4. data/LICENSE +41 -0
  5. data/README.md +559 -0
  6. data/Rakefile +140 -0
  7. data/gource.sh +8 -0
  8. data/images/Anthony Martin.png +0 -0
  9. data/images/Marvin Hofmann.jpg +0 -0
  10. data/images/Marvin Hofmann.png +0 -0
  11. data/lib/bears.rb +17 -0
  12. data/lib/rubybear.rb +45 -0
  13. data/lib/rubybear/account_by_key_api.rb +7 -0
  14. data/lib/rubybear/account_history_api.rb +15 -0
  15. data/lib/rubybear/api.rb +907 -0
  16. data/lib/rubybear/base_error.rb +23 -0
  17. data/lib/rubybear/block_api.rb +14 -0
  18. data/lib/rubybear/broadcast_operations.json +500 -0
  19. data/lib/rubybear/chain.rb +299 -0
  20. data/lib/rubybear/chain_config.rb +22 -0
  21. data/lib/rubybear/chain_stats_api.rb +15 -0
  22. data/lib/rubybear/condenser_api.rb +99 -0
  23. data/lib/rubybear/database_api.rb +5 -0
  24. data/lib/rubybear/error_parser.rb +228 -0
  25. data/lib/rubybear/follow_api.rb +7 -0
  26. data/lib/rubybear/logger.rb +20 -0
  27. data/lib/rubybear/market_history_api.rb +19 -0
  28. data/lib/rubybear/methods.json +498 -0
  29. data/lib/rubybear/mixins/acts_as_poster.rb +124 -0
  30. data/lib/rubybear/mixins/acts_as_voter.rb +50 -0
  31. data/lib/rubybear/mixins/acts_as_wallet.rb +67 -0
  32. data/lib/rubybear/network_broadcast_api.rb +7 -0
  33. data/lib/rubybear/operation.rb +101 -0
  34. data/lib/rubybear/operation_ids.rb +98 -0
  35. data/lib/rubybear/operation_types.rb +139 -0
  36. data/lib/rubybear/stream.rb +527 -0
  37. data/lib/rubybear/tag_api.rb +33 -0
  38. data/lib/rubybear/transaction.rb +306 -0
  39. data/lib/rubybear/type/amount.rb +57 -0
  40. data/lib/rubybear/type/array.rb +17 -0
  41. data/lib/rubybear/type/beneficiaries.rb +29 -0
  42. data/lib/rubybear/type/future.rb +18 -0
  43. data/lib/rubybear/type/hash.rb +17 -0
  44. data/lib/rubybear/type/permission.rb +19 -0
  45. data/lib/rubybear/type/point_in_time.rb +19 -0
  46. data/lib/rubybear/type/price.rb +25 -0
  47. data/lib/rubybear/type/public_key.rb +18 -0
  48. data/lib/rubybear/type/serializer.rb +12 -0
  49. data/lib/rubybear/type/u_int16.rb +19 -0
  50. data/lib/rubybear/type/u_int32.rb +19 -0
  51. data/lib/rubybear/utils.rb +170 -0
  52. data/lib/rubybear/version.rb +4 -0
  53. data/rubybear.gemspec +40 -0
  54. metadata +412 -0
@@ -0,0 +1,139 @@
1
+ module Rubybear
2
+
3
+ # See: https://github.com/bearshares/bears-js/blob/766746adb5ded86380be982c844f4c269f7800ae/src/auth/serializer/src/operations.js
4
+ module OperationTypes
5
+ TYPES = {
6
+ transfer: {
7
+ amount: Type::Amount
8
+ },
9
+ transfer_to_coining: {
10
+ amount: Type::Amount
11
+ },
12
+ withdraw_coining: {
13
+ coining_shares: Type::Amount
14
+ },
15
+ limit_order_create: {
16
+ orderid: Type::Uint32,
17
+ amount_to_sell: Type::Amount,
18
+ min_to_receive: Type::Amount,
19
+ expiration: Type::PointInTime
20
+ },
21
+ limit_order_cancel: {
22
+ orderid: Type::Uint32
23
+ },
24
+ feed_publish: {
25
+ exchange_rate: Type::Price
26
+ },
27
+ convert: {
28
+ requestid: Type::Uint32,
29
+ amount: Type::Amount
30
+ },
31
+ account_create: {
32
+ fee: Type::Amount,
33
+ owner: Type::Permission,
34
+ active: Type::Permission,
35
+ posting: Type::Permission,
36
+ memo: Type::PublicKey
37
+ },
38
+ create_claimed_account: {
39
+ owner: Type::Permission,
40
+ active: Type::Permission,
41
+ posting: Type::Permission,
42
+ memo: Type::PublicKey
43
+ },
44
+ account_update: {
45
+ owner: Type::Permission,
46
+ active: Type::Permission,
47
+ posting: Type::Permission,
48
+ memo: Type::PublicKey
49
+ },
50
+ custom: {
51
+ id: Type::Uint16
52
+ },
53
+ comment_options: {
54
+ max_accepted_payout: Type::Amount,
55
+ allow_replies: Type::Future
56
+ },
57
+ set_withdraw_coining_route: {
58
+ percent: Type::Uint16
59
+ },
60
+ limit_order_create2: {
61
+ orderid: Type::Uint32,
62
+ amount_to_sell: Type::Amount,
63
+ exchange_rate: Type::Price,
64
+ expiration: Type::PointInTime
65
+ },
66
+ request_account_recovery: {
67
+ new_owner_Permission: Type::Permission
68
+ },
69
+ recover_account: {
70
+ new_owner_Permission: Type::Permission,
71
+ recent_owner_Permission: Type::Permission
72
+ },
73
+ escrow_transfer: {
74
+ bsd_amount: Type::Amount,
75
+ bears_amount: Type::Amount,
76
+ escrow_id: Type::Uint32,
77
+ fee: Type::Amount,
78
+ ratification_deadline: Type::PointInTime,
79
+ escrow_expiration: Type::PointInTime
80
+ },
81
+ escrow_dispute: {
82
+ escrow_id: Type::Uint32
83
+ },
84
+ escrow_release: {
85
+ escrow_id: Type::Uint32,
86
+ bsd_amount: Type::Amount,
87
+ bears_amount: Type::Amount
88
+ },
89
+ escrow_approve: {
90
+ escrow_id: Type::Uint32
91
+ },
92
+ transfer_to_savings: {
93
+ amount: Type::Amount
94
+ },
95
+ transfer_from_savings: {
96
+ request_id: Type::Uint32,
97
+ amount: Type::Amount
98
+ },
99
+ cancel_transfer_from_savings: {
100
+ request_id: Type::Uint32
101
+ },
102
+ reset_account: {
103
+ new_owner_permission: Type::Amount
104
+ },
105
+ set_reset_account: {
106
+ reward_bears: Type::Amount,
107
+ reward_bsd: Type::Amount,
108
+ reward_coins: Type::Amount
109
+ },
110
+ claim_reward_balance: {
111
+ reward_bears: Type::Amount,
112
+ reward_bsd: Type::Amount,
113
+ reward_coins: Type::Amount
114
+ },
115
+ delegate_coining_shares: {
116
+ coining_shares: Type::Amount
117
+ },
118
+ claim_account: {
119
+ fee: Type::Amount
120
+ },
121
+ witness_update: {
122
+ block_signing_key: Type::PublicKey,
123
+ props: Type::Array
124
+ },
125
+ witness_set_properties: {
126
+ props: Type::Hash
127
+ }
128
+ }
129
+
130
+ def type(key, param, value)
131
+ return if value.nil?
132
+
133
+ t = TYPES[key] or return value
134
+ p = t[param] or return value
135
+
136
+ p.new(value)
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,527 @@
1
+ module Rubybear
2
+ # Rubybear::Stream allows a live view of the BEARS blockchain.
3
+ #
4
+ # All values returned by `get_dynamic_global_properties` can be streamed.
5
+ #
6
+ # For example, if you want to know which witness is currently signing blocks,
7
+ # use the following:
8
+ #
9
+ # stream = Rubybear::Stream.new
10
+ # stream.current_witness do |witness|
11
+ # puts witness
12
+ # end
13
+ #
14
+ # More importantly, full blocks, transactions, and operations can be streamed.
15
+ class Stream < Api
16
+
17
+ # @private
18
+ INITIAL_TIMEOUT = 0.0200
19
+
20
+ # Note, even though block production is advertised at 3 seconds, often
21
+ # blocks are available in 1.5 seconds. However, we still keep our
22
+ # expectations at 3 seconds.
23
+ # @private
24
+ BLOCK_PRODUCTION = 3.0
25
+
26
+ # @private
27
+ MAX_TIMEOUT = 80
28
+
29
+ # @private
30
+ MAX_BLOCKS_PER_NODE = 10000
31
+
32
+ RANGE_BEHIND_WARNING = 400
33
+
34
+ def initialize(options = {})
35
+ super
36
+ end
37
+
38
+ # Returns the latest operations from the blockchain.
39
+ #
40
+ # stream = Rubybear::Stream.new
41
+ # stream.operations do |op|
42
+ # puts op.to_json
43
+ # end
44
+ #
45
+ # If symbol are passed, then only that operation is returned. Expected
46
+ # symbols are:
47
+ #
48
+ # account_create
49
+ # account_create_with_delegation
50
+ # account_update
51
+ # account_witness_proxy
52
+ # account_witness_vote
53
+ # cancel_transfer_from_savings
54
+ # change_recovery_account
55
+ # claim_reward_balance
56
+ # comment
57
+ # comment_options
58
+ # convert
59
+ # custom
60
+ # custom_json
61
+ # decline_voting_rights
62
+ # delegate_coining_shares
63
+ # delete_comment
64
+ # escrow_approve
65
+ # escrow_dispute
66
+ # escrow_release
67
+ # escrow_transfer
68
+ # feed_publish
69
+ # limit_order_cancel
70
+ # limit_order_create
71
+ # limit_order_create2
72
+ # pow
73
+ # pow2
74
+ # recover_account
75
+ # request_account_recovery
76
+ # set_withdraw_coining_route
77
+ # transfer
78
+ # transfer_from_savings
79
+ # transfer_to_savings
80
+ # transfer_to_coining
81
+ # vote
82
+ # withdraw_coining
83
+ # witness_update
84
+ #
85
+ # For example, to stream only votes:
86
+ #
87
+ # stream = Rubybear::Stream.new
88
+ # stream.operations(:vote) do |vote|
89
+ # puts vote.to_json
90
+ # end
91
+ #
92
+ # You can also stream virtual operations:
93
+ #
94
+ # stream = Rubybear::Stream.new
95
+ # stream.operations(:author_reward) do |vop|
96
+ # puts "#{vop.author} got paid for #{vop.permlink}: #{[vop.bsd_payout, vop.bears_payout, vop.coining_payout]}"
97
+ # end
98
+ #
99
+ # ... or multiple virtual operation types;
100
+ #
101
+ # stream = Rubybear::Stream.new
102
+ # stream.operations([:producer_reward, :author_reward]) do |vop|
103
+ # puts vop
104
+ # end
105
+ #
106
+ # ... or all types, inluding virtual operation types;
107
+ #
108
+ # stream = Rubybear::Stream.new
109
+ # stream.operations(nil, nil, :head, include_virtual: true) do |vop|
110
+ # puts vop
111
+ # end
112
+ #
113
+ # Expected virtual operation types:
114
+ #
115
+ # producer_reward
116
+ # author_reward
117
+ # curation_reward
118
+ # fill_convert_request
119
+ # fill_order
120
+ # fill_coining_withdraw
121
+ # interest
122
+ # shutdown_witness
123
+ #
124
+ # @param type [symbol || ::Array<symbol>] the type(s) of operation, optional.
125
+ # @param start starting block
126
+ # @param mode we have the choice between
127
+ # * :head the last block
128
+ # * :irreversible the block that is confirmed by 2/3 of all block producers and is thus irreversible!
129
+ # @param block the block to execute for each result, optional. Yields: |op, trx_id, block_num, api|
130
+ # @param options [::Hash] additional options
131
+ # @option options [Boollean] :include_virtual Also stream virtual options. Setting this true will impact performance. Default: false.
132
+ # @return [::Hash]
133
+ def operations(type = nil, start = nil, mode = :irreversible, options = {include_virtual: false}, &block)
134
+ type = [type].flatten.compact.map(&:to_sym)
135
+ include_virtual = !!options[:include_virtual]
136
+
137
+ if virtual_op_type?(type)
138
+ include_virtual = true
139
+ end
140
+
141
+ latest_block_number = -1
142
+
143
+ transactions(start, mode) do |transaction, trx_id, block_number|
144
+ virtual_ops_collected = latest_block_number == block_number
145
+ latest_block_number = block_number
146
+
147
+ ops = transaction.operations.map do |t, op|
148
+ t = t.to_sym
149
+ if type.size == 1 && type.first == t
150
+ op
151
+ elsif type.none? || type.include?(t)
152
+ {t => op}
153
+ end
154
+ end.compact
155
+
156
+ if include_virtual && !virtual_ops_collected
157
+ catch :pop_vops do; begin
158
+ api.get_ops_in_block(block_number, true) do |vops, error|
159
+ if !!error
160
+ standby "Node responded with: #{error.message || 'unknown error'}, retrying ...", {
161
+ error: error,
162
+ and: {throw: :pop_vops}
163
+ }
164
+ end
165
+
166
+ vops.each do |vtx|
167
+ next unless defined? vtx.op
168
+
169
+ t = vtx.op.first.to_sym
170
+ op = vtx.op.last
171
+ if type.size == 1 && type.first == t
172
+ ops << op
173
+ elsif type.none? || type.include?(t)
174
+ ops << {t => op}
175
+ end
176
+ end
177
+ end
178
+ end; end
179
+
180
+ virtual_ops_collected = true
181
+ end
182
+
183
+ next if ops.none?
184
+
185
+ return ops unless !!block
186
+
187
+ ops.each do |op|
188
+ yield op, trx_id, block_number, api
189
+ end
190
+ end
191
+ end
192
+
193
+ # Returns the latest transactions from the blockchain.
194
+ #
195
+ # stream = Rubybear::Stream.new
196
+ # stream.transactions do |tx, trx_id|
197
+ # puts "[#{trx_id}] #{tx.to_json}"
198
+ # end
199
+ #
200
+ # @param start starting block
201
+ # @param mode we have the choice between
202
+ # * :head the last block
203
+ # * :irreversible the block that is confirmed by 2/3 of all block producers and is thus irreversible!
204
+ # @param block the block to execute for each result, optional. Yields: |tx, trx_id, api|
205
+ # @return [::Hash]
206
+ def transactions(start = nil, mode = :irreversible, &block)
207
+ blocks(start, mode) do |b, block_number|
208
+ next if (_transactions = b.transactions).nil?
209
+ return _transactions unless !!block
210
+
211
+ _transactions.each_with_index do |transaction, index|
212
+ trx_id = if !!b['transaction_ids']
213
+ b['transaction_ids'][index]
214
+ end
215
+
216
+ yield transaction, trx_id, block_number, api
217
+ end
218
+ end
219
+ end
220
+
221
+ # Returns the latest blocks from the blockchain.
222
+ #
223
+ # stream = Rubybear::Stream.new
224
+ # stream.blocks do |bk, num|
225
+ # puts "[#{num}] #{bk.to_json}"
226
+ # end
227
+ #
228
+ # For convenience and memory management, the api used to poll the current
229
+ # block data is also available inside the block, e.g.:
230
+ #
231
+ # stream = Rubybear::Stream.new
232
+ # stream.blocks do |bk, num, api|
233
+ # puts "[#{num}] #{bk.to_json}"
234
+ #
235
+ # api.get_ops_in_block(num, true) do |vops, error|
236
+ # puts vops
237
+ # end
238
+ # end
239
+ #
240
+ # This idiom is useful for very long running scripts.
241
+ #
242
+ # @param start starting block
243
+ # @param mode we have the choice between
244
+ # * :head the last block
245
+ # * :irreversible the block that is confirmed by 2/3 of all block producers and is thus irreversible!
246
+ # @param max_blocks_per_node the number of blocks to read before trying a new node
247
+ # @param block the block to execute for each result, optional. Yields: |bk, num, api|
248
+ # @return [::Hash]
249
+ def blocks(start = nil, mode = :irreversible, max_blocks_per_node = MAX_BLOCKS_PER_NODE, &block)
250
+ reset_api
251
+
252
+ replay = !!start
253
+ counter = 0
254
+ latest_block_number = -1
255
+ @api_options[:max_requests] = [max_blocks_per_node * 2, @api_options[:max_requests].to_i].max
256
+
257
+ loop do
258
+ break if stop?
259
+
260
+ catch :sequence do; begin
261
+ head_block = api.get_dynamic_global_properties do |properties, error|
262
+ if !!error
263
+ standby "Node responded with: #{error.message || 'unknown error'}, retrying ...", {
264
+ error: error,
265
+ and: {throw: :sequence}
266
+ }
267
+ end
268
+
269
+ break if stop?
270
+
271
+ if properties.head_block_number.nil?
272
+ # This can happen if a reverse proxy is acting up.
273
+ standby "Bad block sequence after height: #{latest_block_number}", {
274
+ and: {throw: :sequence}
275
+ }
276
+ end
277
+
278
+ case mode.to_sym
279
+ when :head then properties.head_block_number
280
+ when :irreversible then properties.last_irreversible_block_num
281
+ else; raise StreamError, '"mode" has to be "head" or "irreversible"'
282
+ end
283
+ end
284
+
285
+ if head_block == latest_block_number
286
+ # This can happen when there's a delay in block production.
287
+
288
+ if current_timeout > BLOCK_PRODUCTION * 6
289
+ standby "Stream has stalled severely ...", {
290
+ and: {backoff: api, throw: :sequence}
291
+ }
292
+ elsif current_timeout > BLOCK_PRODUCTION * 3
293
+ warning "Stream has stalled ..."
294
+ end
295
+
296
+ timeout and throw :sequence
297
+ elsif head_block < latest_block_number
298
+ # This can happen if a reverse proxy is acting up.
299
+ standby "Invalid block sequence at height: #{head_block}", {
300
+ and: {backoff: api, throw: :sequence}
301
+ }
302
+ end
303
+
304
+ reset_timeout
305
+ start ||= head_block
306
+ range = (start..head_block)
307
+
308
+ for n in range
309
+ break if stop?
310
+
311
+ if (counter += 1) > max_blocks_per_node
312
+ reset_api
313
+ counter = 0
314
+ end
315
+
316
+ if !replay && range.size > RANGE_BEHIND_WARNING
317
+ # When the range is above RANGE_BEHIND_WARNING blocks, it's time
318
+ # to warn, unless we're replaying.
319
+
320
+ r = [*range]
321
+ index = r.index(n)
322
+ current_range = r[index..-1]
323
+
324
+ if current_range.size % RANGE_BEHIND_WARNING == 0
325
+ warning "Stream behind by #{current_range.size} blocks (about #{(current_range.size * 3) / 60.0} minutes)."
326
+ end
327
+ end
328
+
329
+ scoped_api, block_options = if use_condenser_namespace?
330
+ [api, n]
331
+ else
332
+ [block_api, {block_num: n}]
333
+ end
334
+
335
+ scoped_api.get_block(n) do |current_block, error|
336
+ if !!error
337
+ if error.message == 'Unable to acquire database lock'
338
+ start = n
339
+ timeout
340
+ standby "Node was unable to acquire database lock, retrying ...", {
341
+ and: {throw: :sequence}
342
+ }
343
+ else
344
+ standby "Node responded with: #{error.message || 'unknown error'}, retrying ...", {
345
+ error: error,
346
+ and: {throw: :sequence}
347
+ }
348
+ end
349
+ elsif current_block.nil?
350
+ standby "Node responded with: empty block, retrying ...", {
351
+ and: {throw: :sequence}
352
+ }
353
+ end
354
+
355
+ latest_block_number = n
356
+ return current_block, n if block.nil?
357
+ yield current_block, n, api
358
+ end
359
+
360
+ start = head_block + 1
361
+ sleep BLOCK_PRODUCTION / range.size
362
+ end
363
+ rescue StreamError; raise
364
+ # rescue => e
365
+ # warning "Unknown streaming error: #{e.inspect}, retrying ... "
366
+ # warning e
367
+ # redo
368
+ end; end
369
+ end
370
+ end
371
+
372
+ # Stops the persistant http connections.
373
+ #
374
+ def shutdown
375
+ flappy = false
376
+
377
+ begin
378
+ unless @api.nil?
379
+ flappy = @api.send(:flappy?)
380
+ @api.shutdown
381
+ end
382
+
383
+ unless @block_api.nil?
384
+ flappy = @block_api.send(:flappy?) unless flappy
385
+ @block_api.shutdown
386
+ end
387
+ rescue => e
388
+ warning("Unable to shut down: #{e}")
389
+ end
390
+
391
+ @api = nil
392
+ @block_api = nil
393
+ GC.start
394
+ end
395
+
396
+ # @private
397
+ def method_names
398
+ @method_names ||= [
399
+ :head_block_number,
400
+ :head_block_id,
401
+ :time,
402
+ :current_witness,
403
+ :total_pow,
404
+ :num_pow_witnesses,
405
+ :virtual_supply,
406
+ :current_supply,
407
+ :confidential_supply,
408
+ :current_bsd_supply,
409
+ :confidential_bsd_supply,
410
+ :total_coining_fund_bears,
411
+ :total_coining_shares,
412
+ :total_reward_fund_bears,
413
+ :total_reward_shares2,
414
+ :total_activity_fund_bears,
415
+ :total_activity_fund_shares,
416
+ :bsd_interest_rate,
417
+ :average_block_size,
418
+ :maximum_block_size,
419
+ :current_aslot,
420
+ :recent_slots_filled,
421
+ :participation_count,
422
+ :last_irreversible_block_num,
423
+ :max_virtual_bandwidth,
424
+ :current_reserve_ratio,
425
+ :block_numbers,
426
+ :blocks
427
+ ].freeze
428
+ end
429
+
430
+ # @private
431
+ def method_params(method)
432
+ case method
433
+ when :block_numbers then {head_block_number: nil}
434
+ when :blocks then {get_block: :head_block_number}
435
+ else; nil
436
+ end
437
+ end
438
+ private
439
+ def method_missing(m, *args, &block)
440
+ super unless respond_to_missing?(m)
441
+
442
+ @latest_values ||= []
443
+ @latest_values.shift(5) if @latest_values.size > 20
444
+ loop do
445
+ break if stop?
446
+
447
+ value = if (n = method_params(m)).nil?
448
+ key_value = api.get_dynamic_global_properties.result[m]
449
+ else
450
+ key = n.keys.first
451
+ if !!n[key]
452
+ r = api.get_dynamic_global_properties.result
453
+ key_value = param = r[n[key]]
454
+ result = nil
455
+ loop do
456
+ break if stop?
457
+
458
+ response = api.send(key, param)
459
+ raise StreamError, JSON[response.error] if !!response.error
460
+ result = response.result
461
+ break if !!result
462
+ warning "#{key}: #{param} result missing, retrying with timeout: #{current_timeout} seconds"
463
+ reset_api
464
+ timeout
465
+ end
466
+ reset_timeout
467
+ result
468
+ else
469
+ key_value = api.get_dynamic_global_properties.result[key]
470
+ end
471
+ end
472
+ unless @latest_values.include? key_value
473
+ @latest_values << key_value
474
+ if !!block
475
+ yield value
476
+ else
477
+ return value
478
+ end
479
+ end
480
+ sleep current_timeout
481
+ end
482
+ end
483
+
484
+ def reset_api
485
+ shutdown
486
+ !!api && !!block_api
487
+ end
488
+
489
+ def timeout
490
+ @timeout ||= INITIAL_TIMEOUT
491
+ @timeout *= 2
492
+ reset_timeout if @timeout > MAX_TIMEOUT
493
+ sleep @timeout || INITIAL_TIMEOUT
494
+ @timeout
495
+ end
496
+
497
+ def current_timeout
498
+ @timeout || INITIAL_TIMEOUT
499
+ end
500
+
501
+ def reset_timeout
502
+ @timeout = nil
503
+ end
504
+
505
+ def virtual_op_type?(type)
506
+ type = [type].flatten.compact.map(&:to_sym)
507
+
508
+ (Rubybear::OperationTypes::TYPES.keys && type).any?
509
+ end
510
+
511
+ def stop?
512
+ @api.nil? || @block_api.nil?
513
+ end
514
+
515
+ def standby(message, options = {})
516
+ error = options[:error]
517
+ secondary = options[:and] || {}
518
+ backoff_api = secondary[:backoff]
519
+ throwable = secondary[:throw]
520
+
521
+ warning message
522
+ warning error if !!error
523
+ backoff_api.send :backoff if !!backoff_api
524
+ throw throwable if !!throwable
525
+ end
526
+ end
527
+ end