hive-ruby 1.0.1 → 1.0.4
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 +4 -4
- data/Rakefile +132 -2
- data/hive-ruby.gemspec +1 -1
- data/lib/hive/api.rb +12 -1
- data/lib/hive/block_api.rb +56 -10
- data/lib/hive/chain_config.rb +5 -2
- data/lib/hive/operation.rb +14 -5
- data/lib/hive/rpc/http_client.rb +5 -4
- data/lib/hive/stream.rb +124 -17
- data/lib/hive/version.rb +1 -1
- data/lib/hive.rb +1 -1
- data/logo.png +0 -0
- metadata +57 -62
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d7242eb82bae34a2adfdf07f2a75886c3b0ac3bfc75a1f5a16733b13dac02562
|
4
|
+
data.tar.gz: c688d90edf39053a2c0b6fc1c8a8093e1dd6d882556d6e55167ae05ea3920348
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 43787bfebc1dd1b7898d41d906647640a5fe7bccc8ac0b9c86a83775827cfb5a09bf286fba4b304eb2173f14f4b247a4f701d67ea5670e63921474f540b92b67
|
7
|
+
data.tar.gz: e9bf8f7589a21a1e1cb72e09de34cf4a6d668ac9aaa565effbe2b4255e086922a6e1af09b0ef691542ab386bb0cc4c19c7b173972d882cd9044194047c51cc8c
|
data/Rakefile
CHANGED
@@ -153,9 +153,13 @@ namespace :stream do
|
|
153
153
|
first_block_num = args[:at_block_num].to_i if !!args[:at_block_num]
|
154
154
|
stream = Hive::Stream.new(url: ENV['TEST_NODE'], mode: mode)
|
155
155
|
api = Hive::Api.new(url: ENV['TEST_NODE'])
|
156
|
+
block_api = Hive::BlockApi.new(url: ENV['TEST_NODE'])
|
156
157
|
last_block_num = nil
|
157
158
|
last_timestamp = nil
|
158
159
|
range_complete = false
|
160
|
+
round_pool = {}
|
161
|
+
aging_blocks = {}
|
162
|
+
aged_block_interval = 630
|
159
163
|
|
160
164
|
api.get_dynamic_global_properties do |properties|
|
161
165
|
current_block_num = if mode == :head
|
@@ -165,13 +169,14 @@ namespace :stream do
|
|
165
169
|
end
|
166
170
|
|
167
171
|
# First pass replays latest a random number of blocks to test chunking.
|
168
|
-
first_block_num ||= current_block_num - (rand *
|
172
|
+
first_block_num ||= current_block_num - (rand * 2000).to_i
|
169
173
|
|
170
174
|
range = first_block_num..current_block_num
|
171
175
|
puts "Initial block range: #{range.size}"
|
172
176
|
|
173
177
|
stream.blocks(at_block_num: range.first) do |block, block_num|
|
174
178
|
current_timestamp = Time.parse(block.timestamp + 'Z')
|
179
|
+
round_pool[current_timestamp] = {block_num: block_num, block: block}
|
175
180
|
|
176
181
|
if !range_complete && block_num > range.last
|
177
182
|
puts 'Done with initial range.'
|
@@ -188,9 +193,35 @@ namespace :stream do
|
|
188
193
|
exit
|
189
194
|
end
|
190
195
|
|
191
|
-
|
196
|
+
round_pool.each do |k, v|
|
197
|
+
aging_blocks[k] = v if Time.now - k > aged_block_interval
|
198
|
+
end
|
199
|
+
|
200
|
+
round_pool = round_pool.select{|k, v| Time.now - k <= aged_block_interval}.to_h
|
201
|
+
drift = last_timestamp.nil? ? 0 : (current_timestamp - last_timestamp) - Hive::Stream::BLOCK_INTERVAL.to_f
|
202
|
+
|
203
|
+
puts "\t#{block_num} Timestamp: #{current_timestamp}, witness: #{block.witness}, aging blocks: #{aging_blocks.size}, drift: #{drift}"
|
204
|
+
|
192
205
|
last_block_num = block_num
|
193
206
|
last_timestamp = current_timestamp
|
207
|
+
|
208
|
+
if range_complete && aging_blocks.any?
|
209
|
+
aging_block_nums = aging_blocks.map{|k, v| v[:block_num]}
|
210
|
+
wire_block_range = (aging_block_nums.first..aging_block_nums.last)
|
211
|
+
|
212
|
+
block_api.get_block_headers(block_range: wire_block_range) do |wire_header, wire_block_num|
|
213
|
+
wire_timestamp = Time.parse(wire_header.timestamp + 'Z')
|
214
|
+
aging_block = aging_blocks[wire_timestamp][:block]
|
215
|
+
|
216
|
+
if wire_header.previous == aging_block.previous
|
217
|
+
puts "\t\tAged block test #{wire_block_num}: √"
|
218
|
+
aging_blocks.delete(wire_timestamp)
|
219
|
+
else
|
220
|
+
puts "\t\tAged block test #{wire_block_num}: detected block-reorganization (#{wire_header.previous} != #{aging_block.previous})"
|
221
|
+
exit
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
194
225
|
end
|
195
226
|
end
|
196
227
|
end
|
@@ -247,6 +278,8 @@ namespace :stream do
|
|
247
278
|
first_block_num = args[:at_block_num].to_i if !!args[:at_block_num]
|
248
279
|
stream = Hive::Stream.new(url: ENV['TEST_NODE'], mode: mode)
|
249
280
|
api = Hive::Api.new(url: ENV['TEST_NODE'])
|
281
|
+
ah_api = Hive::AccountHistoryApi.new(url: ENV['TEST_NODE'])
|
282
|
+
round_vops = {}
|
250
283
|
|
251
284
|
api.get_dynamic_global_properties do |properties|
|
252
285
|
current_block_num = if mode == :head
|
@@ -259,6 +292,31 @@ namespace :stream do
|
|
259
292
|
first_block_num ||= current_block_num - (rand * 200).to_i
|
260
293
|
|
261
294
|
stream.operations(at_block_num: first_block_num, only_virtual: true) do |op, trx_id, block_num|
|
295
|
+
# 126 is about two shuffle rounds (if mode == :head), we need to avoid
|
296
|
+
# the current block_num because we're still in the middle of reading
|
297
|
+
# all of the vops for that block.
|
298
|
+
if round_vops.size > 126 && !round_vops.include?(block_num)
|
299
|
+
ah_api.enum_virtual_ops(block_range_begin: round_vops.keys.min, block_range_end: round_vops.keys.max + 1, include_reversible: true) do |result|
|
300
|
+
round_vops.each do |k, v|
|
301
|
+
later_ops = result.ops.select{|vop| vop.block == k}
|
302
|
+
if (verify_count = later_ops.size) == v.size
|
303
|
+
puts "\t\t#{k} :: streamed vop count was #{v.size} √"
|
304
|
+
else
|
305
|
+
puts "\t\t#{k} :: streamed vop count was #{v.size}, later became #{verify_count}"
|
306
|
+
puts "\t\t\t#{v.map{|op| op.type}.join(', ')}"
|
307
|
+
puts "\t\tLater ops:\n\t\t\t#{later_ops.map{|vop| vop.op.type}.join(', ')}"
|
308
|
+
|
309
|
+
exit
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
round_vops = {}
|
315
|
+
end
|
316
|
+
|
317
|
+
round_vops[block_num] ||= []
|
318
|
+
round_vops[block_num] << op
|
319
|
+
|
262
320
|
puts "#{block_num} :: #{trx_id}; op: #{op.type}"
|
263
321
|
end
|
264
322
|
end
|
@@ -286,6 +344,78 @@ namespace :stream do
|
|
286
344
|
end
|
287
345
|
end
|
288
346
|
end
|
347
|
+
|
348
|
+
desc 'Test the ability to stream all operations (including virtual) that match a pattern.'
|
349
|
+
task :op_pattern, [:pattern, :mode, :at_block_num] do |t, args|
|
350
|
+
mode = (args[:mode] || 'irreversible').to_sym
|
351
|
+
first_block_num = args[:at_block_num].to_i if !!args[:at_block_num]
|
352
|
+
stream = Hive::Stream.new(url: ENV['TEST_NODE'], mode: mode)
|
353
|
+
api = Hive::Api.new(url: ENV['TEST_NODE'])
|
354
|
+
pattern = /#{args[:pattern]}/i
|
355
|
+
|
356
|
+
api.get_dynamic_global_properties do |properties|
|
357
|
+
current_block_num = if mode == :head
|
358
|
+
properties.head_block_number
|
359
|
+
else
|
360
|
+
properties.last_irreversible_block_num
|
361
|
+
end
|
362
|
+
|
363
|
+
# First pass replays latest a random number of blocks to test chunking.
|
364
|
+
first_block_num ||= current_block_num - (rand * 200).to_i
|
365
|
+
|
366
|
+
stream.operations(at_block_num: first_block_num, include_virtual: true) do |op, trx_id, block_num|
|
367
|
+
next unless op.to_json =~ pattern
|
368
|
+
|
369
|
+
puts "#{block_num} :: #{trx_id}; op: #{op.to_json}"
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
desc 'Test the ability to stream all effective_comment_vote_operation operations.'
|
375
|
+
task :effective_comment_vote_operation, [:mode, :at_block_num] do |t, args|
|
376
|
+
mode = (args[:mode] || 'irreversible').to_sym
|
377
|
+
first_block_num = args[:at_block_num].to_i if !!args[:at_block_num]
|
378
|
+
stream = Hive::Stream.new(url: ENV['TEST_NODE'], mode: mode, no_warn: true)
|
379
|
+
api = Hive::Api.new(url: ENV['TEST_NODE'])
|
380
|
+
|
381
|
+
api.get_dynamic_global_properties do |properties|
|
382
|
+
current_block_num = if mode == :head
|
383
|
+
properties.head_block_number
|
384
|
+
else
|
385
|
+
properties.last_irreversible_block_num
|
386
|
+
end
|
387
|
+
|
388
|
+
# First pass replays latest a random number of blocks to test chunking.
|
389
|
+
first_block_num ||= current_block_num - (rand * 200).to_i
|
390
|
+
|
391
|
+
stream.operations(at_block_num: first_block_num, include_virtual: true) do |op, trx_id, block_num|
|
392
|
+
next unless op.type == 'effective_comment_vote_operation'
|
393
|
+
pending_payout = Hive::Type::Amount.new(op.value.pending_payout)
|
394
|
+
|
395
|
+
puts "#{block_num} :: #{trx_id}; voter: #{op.value.voter}, author: #{op.value.author}, pending_payout: #{pending_payout}"
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
desc 'List hardforks.'
|
402
|
+
task :hardforks do
|
403
|
+
database_api = Hive::DatabaseApi.new(url: ENV['TEST_NODE'])
|
404
|
+
block_api = Hive::BlockApi.new(url: ENV['TEST_NODE'])
|
405
|
+
ah_api = Hive::AccountHistoryApi.new(url: ENV['TEST_NODE'])
|
406
|
+
last_hf_timestamp = block_api.get_block(block_num: 1) do |result|
|
407
|
+
Time.parse(result.block.timestamp + 'Z')
|
408
|
+
end
|
409
|
+
|
410
|
+
database_api.get_hardfork_properties do |properties|
|
411
|
+
processed_hardforks = properties.processed_hardforks
|
412
|
+
|
413
|
+
processed_hardforks.each_with_index do |timestamp, index|
|
414
|
+
timestamp = Time.parse(timestamp + 'Z')
|
415
|
+
|
416
|
+
puts "HF#{index}: #{timestamp}"
|
417
|
+
end
|
418
|
+
end
|
289
419
|
end
|
290
420
|
|
291
421
|
YARD::Rake::YardocTask.new do |t|
|
data/hive-ruby.gemspec
CHANGED
@@ -32,7 +32,7 @@ Gem::Specification.new do |spec|
|
|
32
32
|
|
33
33
|
spec.add_dependency 'json', '~> 2.1', '>= 2.1.0'
|
34
34
|
spec.add_dependency 'logging', '~> 2.2', '>= 2.2.0'
|
35
|
-
spec.add_dependency 'hashie', '
|
35
|
+
spec.add_dependency 'hashie', '>= 3.5'
|
36
36
|
spec.add_dependency 'bitcoin-ruby', '~> 0.0', '0.0.20'
|
37
37
|
spec.add_dependency 'ffi', '~> 1.9', '>= 1.9.23'
|
38
38
|
spec.add_dependency 'bindata', '~> 2.4', '>= 2.4.4'
|
data/lib/hive/api.rb
CHANGED
@@ -193,7 +193,18 @@ module Hive
|
|
193
193
|
# Some argument are optional, but if the arguments passed are greater
|
194
194
|
# than the expected arguments size, we can warn.
|
195
195
|
if args_size > expected_args_size
|
196
|
-
|
196
|
+
if rpc_method_name == 'account_history_api.get_account_history' && expected_args_size == 3 && args_size == 6
|
197
|
+
# TODO Remove this condition if they ever fix this issue:
|
198
|
+
# https://gitlab.syncad.com/hive/hive/-/issues/100
|
199
|
+
elsif rpc_method_name == 'account_history_api.get_ops_in_block' && expected_args_size == 2 && args_size == 3
|
200
|
+
# TODO Remove this condition if they ever fix this issue:
|
201
|
+
# https://gitlab.syncad.com/hive/hive/-/issues/100
|
202
|
+
elsif rpc_method_name == 'account_history_api.enum_virtual_ops' && expected_args_size == 2 && args_size == 3
|
203
|
+
# TODO Remove this condition if they ever fix this issue:
|
204
|
+
# https://gitlab.syncad.com/hive/hive/-/issues/100
|
205
|
+
else
|
206
|
+
@error_pipe.puts "Warning #{rpc_method_name} expects arguments: #{expected_args_size}, got: #{args_size}"
|
207
|
+
end
|
197
208
|
end
|
198
209
|
rescue NoMethodError => e
|
199
210
|
error = Hive::ArgumentError.new("#{rpc_method_name} expects arguments: #{expected_args_size}", e)
|
data/lib/hive/block_api.rb
CHANGED
@@ -6,6 +6,8 @@ module Hive
|
|
6
6
|
# Also see: {https://developers.hive.io/apidefinitions/block-api.html Block API Definitions}
|
7
7
|
class BlockApi < Api
|
8
8
|
MAX_RANGE_SIZE = 50
|
9
|
+
MAX_NO_BATCH_RANGE_SIZE = 200
|
10
|
+
MAX_NO_BATCH_NO_RANGE_SIZE = 1
|
9
11
|
|
10
12
|
def initialize(options = {})
|
11
13
|
self.class.api_name = :block_api
|
@@ -20,24 +22,30 @@ module Hive
|
|
20
22
|
get_block_objects(options.merge(object: :block_header), block)
|
21
23
|
end
|
22
24
|
|
23
|
-
# Uses
|
25
|
+
# Uses get_block_range (or batched requsts) on a range of blocks.
|
24
26
|
#
|
25
27
|
# @param options [Hash] The attributes to get a block range with.
|
26
28
|
# @option options [Range] :block_range starting on one block number and ending on an higher block number.
|
27
|
-
|
29
|
+
# @option options [Boolean] :use_batch use json-rpc batch instead of get_block_range (preferred)
|
30
|
+
def get_blocks(options = {block_range: (0..0), use_batch: false}, &block)
|
28
31
|
get_block_objects(options.merge(object: :block), block)
|
29
32
|
end
|
30
33
|
private
|
31
|
-
def get_block_objects(options = {block_range: (0..0)}, block = nil)
|
34
|
+
def get_block_objects(options = {block_range: (0..0), use_batch: false}, block = nil)
|
32
35
|
object = options[:object]
|
33
|
-
object_method = "get_#{object}".to_sym
|
34
36
|
block_range = options[:block_range] || (0..0)
|
37
|
+
use_batch = !!options[:use_batch]
|
38
|
+
|
39
|
+
object = :block_range if object == :block && !use_batch
|
40
|
+
object_method = "get_#{object}".to_sym
|
35
41
|
|
36
|
-
if (start = block_range.first) < 1
|
42
|
+
if !!block_range && block_range.any? && (start = block_range.first) < 1
|
37
43
|
raise Hive::ArgumentError, "Invalid starting block: #{start}"
|
38
44
|
end
|
39
45
|
|
40
|
-
chunks = if
|
46
|
+
chunks = if object == :block_range
|
47
|
+
block_range.each_slice(MAX_NO_BATCH_RANGE_SIZE)
|
48
|
+
elsif block_range.size > MAX_RANGE_SIZE
|
41
49
|
block_range.each_slice(MAX_RANGE_SIZE)
|
42
50
|
else
|
43
51
|
[block_range]
|
@@ -46,27 +54,65 @@ module Hive
|
|
46
54
|
for sub_range in chunks do
|
47
55
|
request_object = []
|
48
56
|
|
49
|
-
|
50
|
-
|
57
|
+
if !!use_batch
|
58
|
+
for i in sub_range do
|
59
|
+
@rpc_client.put(self.class.api_name, object_method, block_num: i, request_object: request_object)
|
60
|
+
end
|
61
|
+
else
|
62
|
+
case object
|
63
|
+
when :block_header
|
64
|
+
# Must use json-rpc batch for block headers request.
|
65
|
+
for i in sub_range do
|
66
|
+
@rpc_client.put(self.class.api_name, :get_block_header, block_num: i, request_object: request_object)
|
67
|
+
end
|
68
|
+
when :block, :block_range
|
69
|
+
if sub_range.size == 1
|
70
|
+
@rpc_client.put(self.class.api_name, :get_block, block_num: sub_range.first, request_object: request_object)
|
71
|
+
else
|
72
|
+
@rpc_client.put(self.class.api_name, :get_block_range, starting_block_num: sub_range.first, count: sub_range.size, request_object: request_object)
|
73
|
+
end
|
74
|
+
end
|
51
75
|
end
|
52
76
|
|
53
77
|
if !!block
|
54
78
|
index = 0
|
55
79
|
@rpc_client.rpc_batch_execute(api_name: self.class.api_name, request_object: request_object) do |result, error, id|
|
80
|
+
raise Hive::RemoteNodeError, error.to_json if !!error
|
81
|
+
|
56
82
|
block_num = sub_range.to_a[index]
|
57
83
|
index = index + 1
|
58
84
|
|
59
85
|
case object
|
60
86
|
when :block_header
|
61
|
-
block.call(result
|
87
|
+
block.call(result[:header], block_num)
|
62
88
|
else
|
63
|
-
|
89
|
+
if !!use_batch || !!result[:block]
|
90
|
+
block.call(result[:block] || result[object], block_num)
|
91
|
+
else
|
92
|
+
current_block_num = block_num
|
93
|
+
result[:blocks].each do |b|
|
94
|
+
# Now verify that the previous block_num really is the
|
95
|
+
# previous block.
|
96
|
+
|
97
|
+
decoded_previous_block_num = b.previous[0..7].to_i(16)
|
98
|
+
previous_block_num = current_block_num - 1
|
99
|
+
|
100
|
+
unless decoded_previous_block_num == previous_block_num
|
101
|
+
raise Hive::RemoteNodeError, "Wrong block_num. Got #{decoded_previous_block_num}, expected #{previous_block_num}"
|
102
|
+
end
|
103
|
+
|
104
|
+
block.call(b, current_block_num)
|
105
|
+
current_block_num = current_block_num + 1
|
106
|
+
end
|
107
|
+
end
|
64
108
|
end
|
65
109
|
end
|
66
110
|
else
|
67
111
|
blocks = []
|
68
112
|
|
69
113
|
@rpc_client.rpc_batch_execute(api_name: self.class.api_name, request_object: request_object) do |result, error, id|
|
114
|
+
raise Hive::RemoteNodeError, error.to_json if !!error
|
115
|
+
|
70
116
|
blocks << result
|
71
117
|
end
|
72
118
|
end
|
data/lib/hive/chain_config.rb
CHANGED
@@ -12,14 +12,17 @@ module Hive
|
|
12
12
|
NETWORKS_HIVE_DEFAULT_NODE = 'https://api.openhive.network'
|
13
13
|
# NETWORKS_HIVE_DEFAULT_NODE = 'https://anyx.io'
|
14
14
|
# NETWORKS_HIVE_DEFAULT_NODE = 'http://anyx.io'
|
15
|
-
# NETWORKS_HIVE_DEFAULT_NODE = 'https://api.hivekings.com'
|
16
15
|
# NETWORKS_HIVE_DEFAULT_NODE = 'https://api.hive.blog'
|
17
16
|
# NETWORKS_HIVE_DEFAULT_NODE = 'https://api.openhive.network'
|
18
17
|
# NETWORKS_HIVE_DEFAULT_NODE = 'https://techcoderx.com'
|
19
|
-
# NETWORKS_HIVE_DEFAULT_NODE = 'https://rpc.
|
18
|
+
# NETWORKS_HIVE_DEFAULT_NODE = 'https://rpc.ecency.com'
|
20
19
|
# NETWORKS_HIVE_DEFAULT_NODE = 'https://hived.privex.io'
|
21
20
|
# NETWORKS_HIVE_DEFAULT_NODE = 'https://api.pharesim.me'
|
22
21
|
# NETWORKS_HIVE_DEFAULT_NODE = 'https://rpc.ausbit.dev'
|
22
|
+
# NETWORKS_HIVE_DEFAULT_NODE = 'https://hived.emre.sh'
|
23
|
+
# NETWORKS_HIVE_DEFAULT_NODE = 'https://api.deathwing.me'
|
24
|
+
# NETWORKS_HIVE_DEFAULT_NODE = 'https://api.c0ff33a.uk'
|
25
|
+
# NETWORKS_HIVE_DEFAULT_NODE = 'https://hive-api.arcange.eu'
|
23
26
|
|
24
27
|
NETWORKS_TEST_CHAIN_ID = '46d82ab7d8db682eb1959aed0ada039a6d49afa1602491f93dde9cac3e8e6c32'
|
25
28
|
NETWORKS_TEST_ADDRESS_PREFIX = 'TST'
|
data/lib/hive/operation.rb
CHANGED
@@ -5,7 +5,7 @@ module Hive
|
|
5
5
|
include Utils
|
6
6
|
|
7
7
|
# IDs derrived from:
|
8
|
-
# https://gitlab.syncad.com/hive/hive/-/blob/master/libraries/protocol/include/
|
8
|
+
# https://gitlab.syncad.com/hive/hive/-/blob/master/libraries/protocol/include/hive/protocol/operations.hpp
|
9
9
|
|
10
10
|
IDS = [
|
11
11
|
:vote_operation,
|
@@ -64,18 +64,18 @@ module Hive
|
|
64
64
|
:create_proposal_operation,
|
65
65
|
:update_proposal_votes_operation,
|
66
66
|
:remove_proposal_operation,
|
67
|
+
:update_proposal_operation,
|
67
68
|
|
68
69
|
# SMT operations
|
69
70
|
:claim_reward_balance2_operation,
|
70
71
|
|
71
72
|
:smt_setup_operation,
|
72
|
-
:smt_cap_reveal_operation,
|
73
|
-
:smt_refund_operation,
|
74
73
|
:smt_setup_emissions_operation,
|
75
74
|
:smt_set_setup_parameters_operation,
|
76
75
|
:smt_set_runtime_parameters_operation,
|
77
76
|
:smt_create_operation,
|
78
|
-
|
77
|
+
:smt_contribute_operation
|
78
|
+
] + VIRTUAL_OP_IDS = [
|
79
79
|
# virtual operations below this point
|
80
80
|
:fill_convert_request_operation,
|
81
81
|
:author_reward_operation,
|
@@ -92,7 +92,16 @@ module Hive
|
|
92
92
|
:return_vesting_delegation_operation,
|
93
93
|
:comment_benefactor_reward_operation,
|
94
94
|
:producer_reward_operation,
|
95
|
-
:clear_null_account_balance_operation
|
95
|
+
:clear_null_account_balance_operation,
|
96
|
+
:proposal_pay_operation,
|
97
|
+
:sps_fund_operation,
|
98
|
+
:hardfork_hive_operation,
|
99
|
+
:hardfork_hive_restore_operation,
|
100
|
+
:delayed_voting_operation,
|
101
|
+
:consolidate_treasury_balance_operation,
|
102
|
+
:effective_comment_vote_operation,
|
103
|
+
:ineffective_delete_comment_operation,
|
104
|
+
:sps_convert_operation
|
96
105
|
]
|
97
106
|
|
98
107
|
def self.op_id(op)
|
data/lib/hive/rpc/http_client.rb
CHANGED
@@ -62,12 +62,13 @@ module Hive
|
|
62
62
|
response = nil
|
63
63
|
|
64
64
|
loop do
|
65
|
+
sub_options = options.dup
|
65
66
|
request = http_post(api_name)
|
66
67
|
|
67
68
|
request_object = if !!api_name && !!api_method
|
68
|
-
put(api_name, api_method,
|
69
|
-
elsif !!options && defined?(
|
70
|
-
|
69
|
+
put(api_name, api_method, sub_options)
|
70
|
+
elsif !!options && defined?(sub_options.delete)
|
71
|
+
sub_options.delete(:request_object)
|
71
72
|
end
|
72
73
|
|
73
74
|
if request_object.size > JSON_RPC_BATCH_SIZE_MAXIMUM
|
@@ -124,7 +125,7 @@ module Hive
|
|
124
125
|
raise_error_response rpc_method_name, rpc_args, r
|
125
126
|
rescue *TIMEOUT_ERRORS => e
|
126
127
|
timeout_detected = true
|
127
|
-
timeout_cause =
|
128
|
+
timeout_cause = JSON[e.message]['error'] + " while posting: #{rpc_args}" rescue e.to_s
|
128
129
|
|
129
130
|
break # fail fast
|
130
131
|
end
|
data/lib/hive/stream.rb
CHANGED
@@ -35,7 +35,9 @@ module Hive
|
|
35
35
|
MAX_RETRY_COUNT = 10
|
36
36
|
|
37
37
|
VOP_TRX_ID = ('0' * 40).freeze
|
38
|
-
|
38
|
+
MAX_VOP_READ_AHEAD = 100
|
39
|
+
SHUFFLE_ROUND_LENGTH = 21
|
40
|
+
|
39
41
|
# @param options [Hash] additional options
|
40
42
|
# @option options [Hive::DatabaseApi] :database_api
|
41
43
|
# @option options [Hive::BlockApi] :block_api
|
@@ -92,7 +94,7 @@ module Hive
|
|
92
94
|
def transactions(options = {}, &block)
|
93
95
|
blocks(options) do |block, block_num|
|
94
96
|
if block.nil?
|
95
|
-
warn "Batch missing block_num: #{block_num}, retrying ..."
|
97
|
+
warn "Batch missing block_num: #{block_num}, retrying ..." unless @no_warn
|
96
98
|
|
97
99
|
block = block_api.get_block(block_num: block_num) do |result|
|
98
100
|
result.block
|
@@ -214,6 +216,10 @@ module Hive
|
|
214
216
|
only_virtual = false
|
215
217
|
include_virtual = false
|
216
218
|
last_block_num = nil
|
219
|
+
within_shuffle_round = nil
|
220
|
+
initial_head_block_number = database_api.get_dynamic_global_properties do |dgpo|
|
221
|
+
dgpo.head_block_number
|
222
|
+
end
|
217
223
|
|
218
224
|
case args.first
|
219
225
|
when Hash
|
@@ -226,7 +232,9 @@ module Hive
|
|
226
232
|
|
227
233
|
if only_virtual
|
228
234
|
block_numbers(options) do |block_num|
|
229
|
-
|
235
|
+
within_shuffle_round ||= initial_head_block_number - block_num < SHUFFLE_ROUND_LENGTH * 2
|
236
|
+
|
237
|
+
get_virtual_ops(types, block_num, within_shuffle_round, block)
|
230
238
|
end
|
231
239
|
else
|
232
240
|
transactions(options) do |transaction, trx_id, block_num|
|
@@ -236,8 +244,9 @@ module Hive
|
|
236
244
|
next unless last_block_num != block_num
|
237
245
|
|
238
246
|
last_block_num = block_num
|
247
|
+
within_shuffle_round ||= initial_head_block_number - block_num < SHUFFLE_ROUND_LENGTH * 2
|
239
248
|
|
240
|
-
get_virtual_ops(types, block_num, block) if include_virtual
|
249
|
+
get_virtual_ops(types, block_num, within_shuffle_round, block) if include_virtual
|
241
250
|
end
|
242
251
|
end
|
243
252
|
end
|
@@ -257,6 +266,7 @@ module Hive
|
|
257
266
|
object = options[:object]
|
258
267
|
object_method = "get_#{object}".to_sym
|
259
268
|
block_interval = BLOCK_INTERVAL
|
269
|
+
use_block_range = true
|
260
270
|
|
261
271
|
at_block_num, until_block_num = if !!block_range = options[:block_range]
|
262
272
|
[block_range.first, block_range.last]
|
@@ -281,9 +291,32 @@ module Hive
|
|
281
291
|
block_interval = BLOCK_INTERVAL
|
282
292
|
end
|
283
293
|
else
|
284
|
-
|
285
|
-
|
286
|
-
|
294
|
+
loop do
|
295
|
+
begin
|
296
|
+
if use_block_range
|
297
|
+
block_api.send(object_method, block_range: range) do |b, n|
|
298
|
+
block.call b, n
|
299
|
+
block_interval = BLOCK_INTERVAL
|
300
|
+
end
|
301
|
+
else
|
302
|
+
range.each do |block_num|
|
303
|
+
block_api.get_block(block_num: block_num) do |b, n|
|
304
|
+
block.call b.block, b.block.block_id[0..7].to_i(16)
|
305
|
+
block_interval = BLOCK_INTERVAL
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
rescue Hive::UnknownError => e
|
310
|
+
if e.message =~ /Could not find method get_block_range/
|
311
|
+
use_block_range = false
|
312
|
+
|
313
|
+
redo
|
314
|
+
end
|
315
|
+
|
316
|
+
raise e
|
317
|
+
end
|
318
|
+
|
319
|
+
break
|
287
320
|
end
|
288
321
|
end
|
289
322
|
|
@@ -325,22 +358,96 @@ module Hive
|
|
325
358
|
end
|
326
359
|
|
327
360
|
# @private
|
328
|
-
def get_virtual_ops(types, block_num, block)
|
361
|
+
def get_virtual_ops(types, block_num, within_shuffle_round, block)
|
329
362
|
retries = 0
|
363
|
+
vop_read_ahead = within_shuffle_round ? 1 : MAX_VOP_READ_AHEAD
|
364
|
+
|
365
|
+
@virtual_ops_cache ||= {}
|
366
|
+
@virtual_ops_cache = @virtual_ops_cache.reject do |k, v|
|
367
|
+
if k < block_num
|
368
|
+
warn "Found orphaned virtual operations for block_num #{k}: #{v.to_json}" unless @no_warn
|
369
|
+
|
370
|
+
true
|
371
|
+
end
|
372
|
+
|
373
|
+
false
|
374
|
+
end
|
330
375
|
|
331
376
|
loop do
|
332
|
-
|
377
|
+
vops_found = false
|
378
|
+
|
379
|
+
if account_history_api.class == Hive::AccountHistoryApi || @enum_virtual_ops_supported.nil? && @enum_virtual_ops_supported != false
|
380
|
+
begin
|
381
|
+
# Use account_history_api.enum_virtual_ops, if supported.
|
382
|
+
|
383
|
+
if @virtual_ops_cache.empty? || !@virtual_ops_cache.keys.include?(block_num)
|
384
|
+
(block_num..(block_num + vop_read_ahead)).each do |block_num|
|
385
|
+
@virtual_ops_cache[block_num] = []
|
386
|
+
end
|
387
|
+
|
388
|
+
enum_virtual_ops_options = {
|
389
|
+
block_range_begin: block_num,
|
390
|
+
block_range_end: block_num + vop_read_ahead,
|
391
|
+
# TODO Use: mode != :irreversible
|
392
|
+
include_reversible: true
|
393
|
+
}
|
394
|
+
|
395
|
+
account_history_api.enum_virtual_ops(enum_virtual_ops_options) do |result|
|
396
|
+
@enum_virtual_ops_supported = true
|
397
|
+
|
398
|
+
result.ops.each do |vop|
|
399
|
+
@virtual_ops_cache[vop.block] << vop
|
400
|
+
end
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
vops_found = true
|
405
|
+
|
406
|
+
if !!@virtual_ops_cache[block_num]
|
407
|
+
@virtual_ops_cache[block_num].each do |vop|
|
408
|
+
next unless block_num == vop.block
|
409
|
+
next if types.any? && !types.include?(vop.op.type)
|
410
|
+
|
411
|
+
if vop.virtual_op == 0
|
412
|
+
# require 'pry' ; binding.pry if vop.op.type == 'producer_reward_operation'
|
413
|
+
warn "Found non-virtual operation (#{vop.op.type}) in enum_virtual_ops result for block: #{block_num}" unless @no_warn
|
414
|
+
|
415
|
+
next
|
416
|
+
end
|
417
|
+
|
418
|
+
block.call vop.op, vop.trx_id, block_num
|
419
|
+
end
|
420
|
+
|
421
|
+
@virtual_ops_cache.delete(block_num)
|
422
|
+
end
|
423
|
+
rescue Hive::UnknownError => e
|
424
|
+
if e.message =~ /This API is not supported for account history backed by Chainbase/
|
425
|
+
warn "Retrying with get_ops_in_block (api does not support enum_virtual_ops)" unless @no_warn
|
426
|
+
@enum_virtual_ops_supported = false
|
427
|
+
vops_found = false
|
428
|
+
else
|
429
|
+
raise e
|
430
|
+
end
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
break if vops_found
|
435
|
+
|
436
|
+
# Fallback to previous method.
|
437
|
+
warn "Retrying with get_ops_in_block (did not find ops for block #{block_num} using enum_virtual_ops)" unless @no_warn
|
438
|
+
|
439
|
+
response = case account_history_api
|
333
440
|
when Hive::CondenserApi
|
334
|
-
|
441
|
+
account_history_api.get_ops_in_block(block_num, true)
|
335
442
|
when Hive::AccountHistoryApi
|
336
|
-
|
443
|
+
account_history_api.get_ops_in_block(
|
337
444
|
block_num: block_num,
|
338
|
-
only_virtual: true
|
339
|
-
|
445
|
+
only_virtual: true,
|
446
|
+
# TODO Use: mode != :irreversible
|
447
|
+
include_reversible: true
|
448
|
+
)
|
340
449
|
end
|
341
450
|
|
342
|
-
response = account_history_api.get_ops_in_block(*get_ops_in_block_options)
|
343
|
-
|
344
451
|
if response.nil? || (result = response.result).nil?
|
345
452
|
if retries < MAX_RETRY_COUNT
|
346
453
|
warn "Retrying get_ops_in_block on block #{block_num}" unless @no_warn
|
@@ -367,7 +474,7 @@ module Hive
|
|
367
474
|
retries = retries + 1
|
368
475
|
redo
|
369
476
|
else
|
370
|
-
warn "unable to find virtual operations for block: #{block_num}"
|
477
|
+
warn "unable to find virtual operations for block: #{block_num}" unless @no_warn
|
371
478
|
# raise TooManyRetriesError, "unable to find virtual operations for block: #{block_num}"
|
372
479
|
end
|
373
480
|
end
|
@@ -375,7 +482,7 @@ module Hive
|
|
375
482
|
ops.each do |op|
|
376
483
|
next if types.any? && !types.include?(op.type)
|
377
484
|
|
378
|
-
block.call op,
|
485
|
+
block.call op, vop.trx_id, block_num
|
379
486
|
end
|
380
487
|
|
381
488
|
break
|
data/lib/hive/version.rb
CHANGED
data/lib/hive.rb
CHANGED
data/logo.png
ADDED
Binary file
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hive-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Anthony Martin
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-07-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -34,42 +34,42 @@ dependencies:
|
|
34
34
|
name: rake
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
|
-
- - ">="
|
38
|
-
- !ruby/object:Gem::Version
|
39
|
-
version: 12.3.0
|
40
37
|
- - "~>"
|
41
38
|
- !ruby/object:Gem::Version
|
42
39
|
version: 13.0.1
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 12.3.0
|
43
43
|
type: :development
|
44
44
|
prerelease: false
|
45
45
|
version_requirements: !ruby/object:Gem::Requirement
|
46
46
|
requirements:
|
47
|
-
- - ">="
|
48
|
-
- !ruby/object:Gem::Version
|
49
|
-
version: 12.3.0
|
50
47
|
- - "~>"
|
51
48
|
- !ruby/object:Gem::Version
|
52
49
|
version: 13.0.1
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 12.3.0
|
53
53
|
- !ruby/object:Gem::Dependency
|
54
54
|
name: minitest
|
55
55
|
requirement: !ruby/object:Gem::Requirement
|
56
56
|
requirements:
|
57
|
-
- - ">="
|
58
|
-
- !ruby/object:Gem::Version
|
59
|
-
version: 5.10.3
|
60
57
|
- - "~>"
|
61
58
|
- !ruby/object:Gem::Version
|
62
59
|
version: '5.14'
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 5.10.3
|
63
63
|
type: :development
|
64
64
|
prerelease: false
|
65
65
|
version_requirements: !ruby/object:Gem::Requirement
|
66
66
|
requirements:
|
67
|
-
- - ">="
|
68
|
-
- !ruby/object:Gem::Version
|
69
|
-
version: 5.10.3
|
70
67
|
- - "~>"
|
71
68
|
- !ruby/object:Gem::Version
|
72
69
|
version: '5.14'
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: 5.10.3
|
73
73
|
- !ruby/object:Gem::Dependency
|
74
74
|
name: minitest-line
|
75
75
|
requirement: !ruby/object:Gem::Requirement
|
@@ -94,42 +94,42 @@ dependencies:
|
|
94
94
|
name: minitest-proveit
|
95
95
|
requirement: !ruby/object:Gem::Requirement
|
96
96
|
requirements:
|
97
|
-
- - ">="
|
98
|
-
- !ruby/object:Gem::Version
|
99
|
-
version: 1.0.0
|
100
97
|
- - "~>"
|
101
98
|
- !ruby/object:Gem::Version
|
102
99
|
version: '1.0'
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: 1.0.0
|
103
103
|
type: :development
|
104
104
|
prerelease: false
|
105
105
|
version_requirements: !ruby/object:Gem::Requirement
|
106
106
|
requirements:
|
107
|
-
- - ">="
|
108
|
-
- !ruby/object:Gem::Version
|
109
|
-
version: 1.0.0
|
110
107
|
- - "~>"
|
111
108
|
- !ruby/object:Gem::Version
|
112
109
|
version: '1.0'
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: 1.0.0
|
113
113
|
- !ruby/object:Gem::Dependency
|
114
114
|
name: webmock
|
115
115
|
requirement: !ruby/object:Gem::Requirement
|
116
116
|
requirements:
|
117
|
-
- - ">="
|
118
|
-
- !ruby/object:Gem::Version
|
119
|
-
version: 3.3.0
|
120
117
|
- - "~>"
|
121
118
|
- !ruby/object:Gem::Version
|
122
119
|
version: '3.3'
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: 3.3.0
|
123
123
|
type: :development
|
124
124
|
prerelease: false
|
125
125
|
version_requirements: !ruby/object:Gem::Requirement
|
126
126
|
requirements:
|
127
|
-
- - ">="
|
128
|
-
- !ruby/object:Gem::Version
|
129
|
-
version: 3.3.0
|
130
127
|
- - "~>"
|
131
128
|
- !ruby/object:Gem::Version
|
132
129
|
version: '3.3'
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: 3.3.0
|
133
133
|
- !ruby/object:Gem::Dependency
|
134
134
|
name: simplecov
|
135
135
|
requirement: !ruby/object:Gem::Requirement
|
@@ -154,22 +154,22 @@ dependencies:
|
|
154
154
|
name: vcr
|
155
155
|
requirement: !ruby/object:Gem::Requirement
|
156
156
|
requirements:
|
157
|
-
- - ">="
|
158
|
-
- !ruby/object:Gem::Version
|
159
|
-
version: 4.0.0
|
160
157
|
- - "~>"
|
161
158
|
- !ruby/object:Gem::Version
|
162
159
|
version: '6.0'
|
160
|
+
- - ">="
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
version: 4.0.0
|
163
163
|
type: :development
|
164
164
|
prerelease: false
|
165
165
|
version_requirements: !ruby/object:Gem::Requirement
|
166
166
|
requirements:
|
167
|
-
- - ">="
|
168
|
-
- !ruby/object:Gem::Version
|
169
|
-
version: 4.0.0
|
170
167
|
- - "~>"
|
171
168
|
- !ruby/object:Gem::Version
|
172
169
|
version: '6.0'
|
170
|
+
- - ">="
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: 4.0.0
|
173
173
|
- !ruby/object:Gem::Dependency
|
174
174
|
name: yard
|
175
175
|
requirement: !ruby/object:Gem::Requirement
|
@@ -214,22 +214,22 @@ dependencies:
|
|
214
214
|
name: awesome_print
|
215
215
|
requirement: !ruby/object:Gem::Requirement
|
216
216
|
requirements:
|
217
|
-
- - ">="
|
218
|
-
- !ruby/object:Gem::Version
|
219
|
-
version: 1.8.0
|
220
217
|
- - "~>"
|
221
218
|
- !ruby/object:Gem::Version
|
222
219
|
version: '1.8'
|
220
|
+
- - ">="
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: 1.8.0
|
223
223
|
type: :development
|
224
224
|
prerelease: false
|
225
225
|
version_requirements: !ruby/object:Gem::Requirement
|
226
226
|
requirements:
|
227
|
-
- - ">="
|
228
|
-
- !ruby/object:Gem::Version
|
229
|
-
version: 1.8.0
|
230
227
|
- - "~>"
|
231
228
|
- !ruby/object:Gem::Version
|
232
229
|
version: '1.8'
|
230
|
+
- - ">="
|
231
|
+
- !ruby/object:Gem::Version
|
232
|
+
version: 1.8.0
|
233
233
|
- !ruby/object:Gem::Dependency
|
234
234
|
name: irb
|
235
235
|
requirement: !ruby/object:Gem::Requirement
|
@@ -254,62 +254,56 @@ dependencies:
|
|
254
254
|
name: json
|
255
255
|
requirement: !ruby/object:Gem::Requirement
|
256
256
|
requirements:
|
257
|
-
- - ">="
|
258
|
-
- !ruby/object:Gem::Version
|
259
|
-
version: 2.1.0
|
260
257
|
- - "~>"
|
261
258
|
- !ruby/object:Gem::Version
|
262
259
|
version: '2.1'
|
260
|
+
- - ">="
|
261
|
+
- !ruby/object:Gem::Version
|
262
|
+
version: 2.1.0
|
263
263
|
type: :runtime
|
264
264
|
prerelease: false
|
265
265
|
version_requirements: !ruby/object:Gem::Requirement
|
266
266
|
requirements:
|
267
|
-
- - ">="
|
268
|
-
- !ruby/object:Gem::Version
|
269
|
-
version: 2.1.0
|
270
267
|
- - "~>"
|
271
268
|
- !ruby/object:Gem::Version
|
272
269
|
version: '2.1'
|
270
|
+
- - ">="
|
271
|
+
- !ruby/object:Gem::Version
|
272
|
+
version: 2.1.0
|
273
273
|
- !ruby/object:Gem::Dependency
|
274
274
|
name: logging
|
275
275
|
requirement: !ruby/object:Gem::Requirement
|
276
276
|
requirements:
|
277
|
-
- - ">="
|
278
|
-
- !ruby/object:Gem::Version
|
279
|
-
version: 2.2.0
|
280
277
|
- - "~>"
|
281
278
|
- !ruby/object:Gem::Version
|
282
279
|
version: '2.2'
|
280
|
+
- - ">="
|
281
|
+
- !ruby/object:Gem::Version
|
282
|
+
version: 2.2.0
|
283
283
|
type: :runtime
|
284
284
|
prerelease: false
|
285
285
|
version_requirements: !ruby/object:Gem::Requirement
|
286
286
|
requirements:
|
287
|
-
- - ">="
|
288
|
-
- !ruby/object:Gem::Version
|
289
|
-
version: 2.2.0
|
290
287
|
- - "~>"
|
291
288
|
- !ruby/object:Gem::Version
|
292
289
|
version: '2.2'
|
290
|
+
- - ">="
|
291
|
+
- !ruby/object:Gem::Version
|
292
|
+
version: 2.2.0
|
293
293
|
- !ruby/object:Gem::Dependency
|
294
294
|
name: hashie
|
295
295
|
requirement: !ruby/object:Gem::Requirement
|
296
296
|
requirements:
|
297
|
-
- - "~>"
|
298
|
-
- !ruby/object:Gem::Version
|
299
|
-
version: '3.5'
|
300
297
|
- - ">="
|
301
298
|
- !ruby/object:Gem::Version
|
302
|
-
version: 3.5
|
299
|
+
version: '3.5'
|
303
300
|
type: :runtime
|
304
301
|
prerelease: false
|
305
302
|
version_requirements: !ruby/object:Gem::Requirement
|
306
303
|
requirements:
|
307
|
-
- - "~>"
|
308
|
-
- !ruby/object:Gem::Version
|
309
|
-
version: '3.5'
|
310
304
|
- - ">="
|
311
305
|
- !ruby/object:Gem::Version
|
312
|
-
version: 3.5
|
306
|
+
version: '3.5'
|
313
307
|
- !ruby/object:Gem::Dependency
|
314
308
|
name: bitcoin-ruby
|
315
309
|
requirement: !ruby/object:Gem::Requirement
|
@@ -482,11 +476,12 @@ files:
|
|
482
476
|
- lib/hive/type/base_type.rb
|
483
477
|
- lib/hive/utils.rb
|
484
478
|
- lib/hive/version.rb
|
479
|
+
- logo.png
|
485
480
|
homepage: https://gitlab.syncad.com/hive/hive-ruby
|
486
481
|
licenses:
|
487
482
|
- MIT
|
488
483
|
metadata: {}
|
489
|
-
post_install_message:
|
484
|
+
post_install_message:
|
490
485
|
rdoc_options: []
|
491
486
|
require_paths:
|
492
487
|
- lib
|
@@ -501,8 +496,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
501
496
|
- !ruby/object:Gem::Version
|
502
497
|
version: '0'
|
503
498
|
requirements: []
|
504
|
-
rubygems_version: 3.
|
505
|
-
signing_key:
|
499
|
+
rubygems_version: 3.2.3
|
500
|
+
signing_key:
|
506
501
|
specification_version: 4
|
507
502
|
summary: Hive Ruby Client
|
508
503
|
test_files: []
|