radiator 0.3.9 → 0.3.10
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/Gemfile.lock +2 -2
- data/Rakefile +11 -4
- data/lib/golos.rb +8 -0
- data/lib/radiator/api.rb +91 -29
- data/lib/radiator/base_error.rb +1 -0
- data/lib/radiator/chain.rb +335 -0
- data/lib/radiator/error_parser.rb +81 -42
- data/lib/radiator/stream.rb +54 -12
- data/lib/radiator/transaction.rb +6 -3
- data/lib/radiator/utils.rb +1 -1
- data/lib/radiator/version.rb +2 -1
- data/lib/radiator.rb +3 -0
- data/lib/steem.rb +8 -0
- data/radiator.gemspec +4 -2
- metadata +5 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c4ff8172c52320522f9a22b4a710a1051837aa9d
|
4
|
+
data.tar.gz: 827457f802605807383f2c42350f671754fad7c0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f79ed7cbb0e5a64a7a21c17e6b25bc254591b9e31b24ab733ffc93872de6949a994339844f4479780c86e34ac56546dbfb91d391875c861fc03ea4818f83c9a1
|
7
|
+
data.tar.gz: 53c4aa2777f751221c0e0bee02b2dc47abbed64dde1ae92d66db77ae57a4363d5554d552012f1372b480bd928c6f17ea07b0595f59d104397a3562e66de5a09b
|
data/Gemfile.lock
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
radiator (0.3.
|
4
|
+
radiator (0.3.10)
|
5
5
|
awesome_print (~> 1.7, >= 1.7.0)
|
6
6
|
bitcoin-ruby (~> 0.0, >= 0.0.11)
|
7
7
|
ffi (~> 1.9, >= 1.9.18)
|
8
8
|
hashie (~> 3.5, >= 3.5.5)
|
9
9
|
json (~> 2.0, >= 2.0.2)
|
10
10
|
logging (~> 2.2, >= 2.2.0)
|
11
|
-
net-http-persistent (
|
11
|
+
net-http-persistent (>= 2.5.2)
|
12
12
|
|
13
13
|
GEM
|
14
14
|
remote: https://rubygems.org/
|
data/Rakefile
CHANGED
@@ -52,16 +52,21 @@ task :test_live_broadcast, [:account, :wif, :chain] do |t, args|
|
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
55
|
-
desc 'Tests the ability to stream live data.'
|
56
|
-
task :test_live_stream, :chain do |t, args|
|
55
|
+
desc 'Tests the ability to stream live data. defaults: chain = steem; persist = true.'
|
56
|
+
task :test_live_stream, [:chain, :persist] do |t, args|
|
57
57
|
chain = args[:chain] || 'steem'
|
58
|
+
persist = (args[:persist] || 'true') == 'true'
|
58
59
|
last_block_number = 0
|
59
|
-
options = {chain: chain}
|
60
|
+
options = {chain: chain, persist: persist}
|
60
61
|
api = Radiator::Api.new(options)
|
61
62
|
total_ops = 0.0
|
62
63
|
total_vops = 0.0
|
64
|
+
elapsed = 0
|
65
|
+
count = 0
|
63
66
|
|
64
67
|
Radiator::Stream.new(options).blocks do |b, n|
|
68
|
+
start = Time.now.utc
|
69
|
+
|
65
70
|
if last_block_number == 0
|
66
71
|
# skip test
|
67
72
|
elsif last_block_number + 1 == n
|
@@ -80,7 +85,9 @@ task :test_live_stream, :chain do |t, args|
|
|
80
85
|
0
|
81
86
|
end
|
82
87
|
|
83
|
-
|
88
|
+
elapsed += Time.now.utc - start
|
89
|
+
count += 1
|
90
|
+
puts "#{n}: #{b.witness}; trx: #{t_size}; op: #{op_size}, vop: #{vop_size} (cumulative vop ratio: #{('%.2f' % (vop_ratio * 100))} %; average #{((elapsed / count) * 1000).to_i}ms)"
|
84
91
|
end
|
85
92
|
else
|
86
93
|
# This should not happen. If it does, there's likely a bug in Radiator.
|
data/lib/golos.rb
ADDED
data/lib/radiator/api.rb
CHANGED
@@ -150,12 +150,17 @@ module Radiator
|
|
150
150
|
|
151
151
|
DEFAULT_GOLOS_FAILOVER_URLS = [
|
152
152
|
DEFAULT_GOLOS_URL,
|
153
|
-
'https://api.golos.cf'
|
153
|
+
'https://api.golos.cf',
|
154
|
+
# not recommended:
|
155
|
+
# 'http://golos-seed.arcange.eu',
|
156
|
+
# not recommended, requires option ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE
|
157
|
+
# 'https://golos-seed.arcange.eu',
|
154
158
|
]
|
155
159
|
|
156
160
|
# @private
|
157
161
|
POST_HEADERS = {
|
158
|
-
'Content-Type' => 'application/json'
|
162
|
+
'Content-Type' => 'application/json',
|
163
|
+
'User-Agent' => Radiator::AGENT_ID
|
159
164
|
}
|
160
165
|
|
161
166
|
# @private
|
@@ -203,7 +208,6 @@ module Radiator
|
|
203
208
|
@hashie_logger = options[:hashie_logger] || Logger.new(nil)
|
204
209
|
@max_requests = options[:max_requests] || 30
|
205
210
|
@ssl_verify_mode = options[:ssl_verify_mode] || OpenSSL::SSL::VERIFY_PEER
|
206
|
-
@reuse_ssl_sessions = !!options[:reuse_ssl_sessions]
|
207
211
|
@ssl_version = options[:ssl_version]
|
208
212
|
|
209
213
|
if @failover_urls.nil?
|
@@ -223,17 +227,31 @@ module Radiator
|
|
223
227
|
true
|
224
228
|
end
|
225
229
|
|
230
|
+
@persist = if options.keys.include? :persist
|
231
|
+
options[:persist]
|
232
|
+
else
|
233
|
+
true
|
234
|
+
end
|
235
|
+
|
236
|
+
@reuse_ssl_sessions = if options.keys.include? :reuse_ssl_sessions
|
237
|
+
options[:reuse_ssl_sessions]
|
238
|
+
else
|
239
|
+
true
|
240
|
+
end
|
241
|
+
|
226
242
|
if defined? Net::HTTP::Persistent::DEFAULT_POOL_SIZE
|
227
243
|
@pool_size = options[:pool_size] || Net::HTTP::Persistent::DEFAULT_POOL_SIZE
|
228
244
|
end
|
229
245
|
|
230
246
|
Hashie.logger = @hashie_logger
|
231
247
|
@method_names = nil
|
232
|
-
@
|
248
|
+
@http_memo = {}
|
233
249
|
@api_options = options.dup.merge(chain: @chain)
|
234
250
|
@api = nil
|
235
251
|
@block_api = nil
|
236
252
|
@backoff_at = nil
|
253
|
+
|
254
|
+
ObjectSpace.define_finalizer(self, self.class.finalize(self))
|
237
255
|
end
|
238
256
|
|
239
257
|
# Get a specific block or range of blocks.
|
@@ -355,8 +373,13 @@ module Radiator
|
|
355
373
|
def shutdown
|
356
374
|
@uri = nil
|
357
375
|
@http_id = nil
|
358
|
-
@
|
359
|
-
|
376
|
+
@http_memo.each do |k|
|
377
|
+
v = @http_memo.delete(k)
|
378
|
+
if defined?(v.shutdown)
|
379
|
+
debug "Shutting down instance #{k} (#{v})"
|
380
|
+
v.shutdown
|
381
|
+
end
|
382
|
+
end
|
360
383
|
@api.shutdown if !!@api && @api != self
|
361
384
|
@api = nil
|
362
385
|
@block_api.shutdown if !!@block_api && @block_api != self
|
@@ -485,6 +508,19 @@ module Radiator
|
|
485
508
|
backoff
|
486
509
|
end # loop
|
487
510
|
end
|
511
|
+
|
512
|
+
def inspect
|
513
|
+
properties = %w(
|
514
|
+
chain url backoff_at max_requests ssl_verify_mode ssl_version persist
|
515
|
+
recover_transactions_on_error reuse_ssl_sessions pool_size
|
516
|
+
).map do |prop|
|
517
|
+
if !!(v = instance_variable_get("@#{prop}"))
|
518
|
+
"@#{prop}=#{v}"
|
519
|
+
end
|
520
|
+
end.compact.join(', ')
|
521
|
+
|
522
|
+
"#<#{self.class.name} [#{properties}]>"
|
523
|
+
end
|
488
524
|
private
|
489
525
|
def self.methods_json_path
|
490
526
|
@methods_json_path ||= "#{File.dirname(__FILE__)}/methods.json"
|
@@ -497,6 +533,14 @@ module Radiator
|
|
497
533
|
end.compact.freeze
|
498
534
|
end
|
499
535
|
|
536
|
+
def self.apply_http_defaults(http, ssl_verify_mode)
|
537
|
+
http.read_timeout = 10
|
538
|
+
http.open_timeout = 10
|
539
|
+
http.verify_mode = ssl_verify_mode
|
540
|
+
http.ssl_timeout = 30
|
541
|
+
http
|
542
|
+
end
|
543
|
+
|
500
544
|
def api
|
501
545
|
@api ||= self.class == Api ? self : Api.new(@api_options)
|
502
546
|
end
|
@@ -519,26 +563,32 @@ module Radiator
|
|
519
563
|
end
|
520
564
|
|
521
565
|
def http
|
522
|
-
|
566
|
+
return @http_memo[http_id] if @http_memo.keys.include? http_id
|
567
|
+
|
568
|
+
@http_memo[http_id] = if @persist
|
569
|
+
idempotent = api_name != :network_broadcast_api
|
570
|
+
|
571
|
+
http = if defined? Net::HTTP::Persistent::DEFAULT_POOL_SIZE
|
572
|
+
Net::HTTP::Persistent.new(name: http_id, pool_size: @pool_size)
|
573
|
+
else
|
574
|
+
# net-http-persistent < 3.0
|
575
|
+
Net::HTTP::Persistent.new(http_id)
|
576
|
+
end
|
577
|
+
|
578
|
+
http.keep_alive = 30
|
579
|
+
http.idle_timeout = idempotent ? 10 : nil
|
580
|
+
http.max_requests = @max_requests
|
581
|
+
http.retry_change_requests = idempotent
|
582
|
+
http.reuse_ssl_sessions = @reuse_ssl_sessions
|
523
583
|
|
524
|
-
|
525
|
-
Net::HTTP::Persistent.new(name: http_id, pool_size: @pool_size)
|
584
|
+
http
|
526
585
|
else
|
527
|
-
|
528
|
-
|
586
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
587
|
+
http.use_ssl = uri.scheme == 'https'
|
588
|
+
http
|
529
589
|
end
|
530
590
|
|
531
|
-
@
|
532
|
-
@http.read_timeout = 10
|
533
|
-
@http.open_timeout = 10
|
534
|
-
@http.idle_timeout = idempotent ? 10 : nil
|
535
|
-
@http.max_requests = @max_requests
|
536
|
-
@http.retry_change_requests = idempotent
|
537
|
-
@http.verify_mode = @ssl_verify_mode
|
538
|
-
@http.reuse_ssl_sessions = @reuse_ssl_sessions
|
539
|
-
@http.ssl_version = @ssl_version
|
540
|
-
|
541
|
-
@http
|
591
|
+
Api::apply_http_defaults(@http_memo[http_id], @ssl_verify_mode)
|
542
592
|
end
|
543
593
|
|
544
594
|
def post_request
|
@@ -548,7 +598,12 @@ module Radiator
|
|
548
598
|
def request(options)
|
549
599
|
request = post_request
|
550
600
|
request.body = JSON[options]
|
551
|
-
|
601
|
+
|
602
|
+
case http
|
603
|
+
when Net::HTTP::Persistent then http.request(uri, request)
|
604
|
+
when Net::HTTP then http.request(request)
|
605
|
+
else; raise ApiError, "Unsuppored scheme: #{http.inspect}"
|
606
|
+
end
|
552
607
|
end
|
553
608
|
|
554
609
|
def recover_transaction(signatures, expected_rpc_id, after)
|
@@ -610,7 +665,7 @@ module Radiator
|
|
610
665
|
@failover_urls.delete(url)
|
611
666
|
end
|
612
667
|
|
613
|
-
url || @url
|
668
|
+
url || (uri || @url).to_s
|
614
669
|
end
|
615
670
|
|
616
671
|
def bump_failover
|
@@ -625,11 +680,11 @@ module Radiator
|
|
625
680
|
|
626
681
|
def drop_current_failover_url(prefix)
|
627
682
|
if @preferred_failover_urls.size == 1
|
628
|
-
warning "Node #{
|
683
|
+
warning "Node #{uri} appears to be misconfigured but no other node is available, retrying ...", prefix
|
629
684
|
else
|
630
|
-
warning "Removing misconfigured node from failover urls: #{
|
631
|
-
@preferred_failover_urls.delete(
|
632
|
-
@failover_urls.delete(
|
685
|
+
warning "Removing misconfigured node from failover urls: #{uri}, retrying ...", prefix
|
686
|
+
@preferred_failover_urls.delete(uri)
|
687
|
+
@failover_urls.delete(uri)
|
633
688
|
end
|
634
689
|
end
|
635
690
|
|
@@ -700,7 +755,7 @@ module Radiator
|
|
700
755
|
|
701
756
|
def backoff
|
702
757
|
shutdown
|
703
|
-
bump_failover if flappy? || !healthy?(
|
758
|
+
bump_failover if flappy? || !healthy?(uri)
|
704
759
|
@backoff_at ||= Time.now.utc
|
705
760
|
@backoff_sleep ||= 0.01
|
706
761
|
|
@@ -712,5 +767,12 @@ module Radiator
|
|
712
767
|
@backoff_sleep = nil
|
713
768
|
end
|
714
769
|
end
|
770
|
+
|
771
|
+
def self.finalize(obj)
|
772
|
+
proc {
|
773
|
+
puts "DESTROY OBJECT #{obj.inspect}" if ENV['LOG'] == 'TRACE'
|
774
|
+
obj.shutdown
|
775
|
+
}
|
776
|
+
end
|
715
777
|
end
|
716
778
|
end
|
data/lib/radiator/base_error.rb
CHANGED
@@ -20,3 +20,4 @@ module Radiator; class StreamError < BaseError; end; end
|
|
20
20
|
module Radiator; class TypeError < BaseError; end; end
|
21
21
|
module Radiator; class OperationError < BaseError; end; end
|
22
22
|
module Radiator; class TransactionError < BaseError; end; end
|
23
|
+
module Radiator; class ChainError < BaseError; end; end
|
@@ -0,0 +1,335 @@
|
|
1
|
+
module Radiator
|
2
|
+
# Examples ...
|
3
|
+
#
|
4
|
+
# To vote on a post/comment:
|
5
|
+
#
|
6
|
+
# steem = Steem.new(account_name: 'your account name', wif: 'your wif')
|
7
|
+
# steem.vote!(10000, 'author', 'post-or-comment-permlink')
|
8
|
+
#
|
9
|
+
# To post and vote in the same transaction:
|
10
|
+
#
|
11
|
+
# steem = Steem.new(account_name: 'your account name', wif: 'your wif')
|
12
|
+
# steem.post!(title: 'title of my post', body: 'body of my post', tags: ['tag'], self_upvote: 10000)
|
13
|
+
#
|
14
|
+
# To post and vote with declined payout:
|
15
|
+
#
|
16
|
+
# steem = Steem.new(account_name: 'your account name', wif: 'your wif')
|
17
|
+
#
|
18
|
+
# options = {
|
19
|
+
# title: 'title of my post',
|
20
|
+
# body: 'body of my post',
|
21
|
+
# tags: ['tag'],
|
22
|
+
# self_upvote: 10000,
|
23
|
+
# percent_steem_dollars: 0
|
24
|
+
# }
|
25
|
+
#
|
26
|
+
# steem.post!(options)
|
27
|
+
#
|
28
|
+
class Chain
|
29
|
+
VALID_OPTIONS = %w(
|
30
|
+
chain account_name wif
|
31
|
+
).map(&:to_sym)
|
32
|
+
VALID_OPTIONS.each { |option| attr_accessor option }
|
33
|
+
|
34
|
+
def initialize(options = {})
|
35
|
+
options = options.dup
|
36
|
+
options.each do |k, v|
|
37
|
+
k = k.to_sym
|
38
|
+
if VALID_OPTIONS.include?(k.to_sym)
|
39
|
+
options.delete(k)
|
40
|
+
send("#{k}=", v)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
@account_name ||= ENV['ACCOUNT_NAME']
|
45
|
+
@wif ||= ENV['WIF']
|
46
|
+
|
47
|
+
raise ChainError, "Required option: chain" if @chain.nil?
|
48
|
+
raise ChainError, "Required option: account_name, wif" if @account_name.nil? || @wif.nil?
|
49
|
+
|
50
|
+
reset
|
51
|
+
end
|
52
|
+
|
53
|
+
# Clears out queued operations.
|
54
|
+
def reset
|
55
|
+
@operations = []
|
56
|
+
end
|
57
|
+
|
58
|
+
# Broadcast queued operations.
|
59
|
+
#
|
60
|
+
# @param auto_reset [boolean] clears operations no matter what, even if there's an error.
|
61
|
+
def broadcast!(auto_reset = false)
|
62
|
+
begin
|
63
|
+
transaction = Radiator::Transaction.new(build_options)
|
64
|
+
transaction.operations = @operations
|
65
|
+
response = transaction.process(true)
|
66
|
+
rescue => e
|
67
|
+
reset if auto_reset
|
68
|
+
raise e
|
69
|
+
end
|
70
|
+
|
71
|
+
if !!response.result
|
72
|
+
reset
|
73
|
+
response
|
74
|
+
else
|
75
|
+
reset if auto_reset
|
76
|
+
ErrorParser.new(response)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Create a vote operation.
|
81
|
+
#
|
82
|
+
# Examples:
|
83
|
+
#
|
84
|
+
# steem = Steem.new(account_name: 'your account name', wif: 'your wif')
|
85
|
+
# steem.vote(10000, 'author', 'permlink')
|
86
|
+
# steem.broadcast!
|
87
|
+
#
|
88
|
+
# ... or ...
|
89
|
+
#
|
90
|
+
# steem = Steem.new(account_name: 'your account name', wif: 'your wif')
|
91
|
+
# steem.vote(10000, '@author/permlink')
|
92
|
+
# steem.broadcast!
|
93
|
+
#
|
94
|
+
# @param weight [Integer] value between -10000 and 10000.
|
95
|
+
# @param args [author, permlink || slug] pass either `author` and `permlink` or string containing both like `@author/permlink`.
|
96
|
+
def vote(weight, *args)
|
97
|
+
author, permlink = if args.size == 1
|
98
|
+
author, permlink = parse_slug(args[0])
|
99
|
+
else
|
100
|
+
author, permlink = args
|
101
|
+
end
|
102
|
+
|
103
|
+
@operations << {
|
104
|
+
type: :vote,
|
105
|
+
voter: account_name,
|
106
|
+
author: author,
|
107
|
+
permlink: permlink,
|
108
|
+
weight: weight
|
109
|
+
}
|
110
|
+
|
111
|
+
self
|
112
|
+
end
|
113
|
+
|
114
|
+
# Create a vote operation and broadcasts it right away.
|
115
|
+
#
|
116
|
+
# Examples:
|
117
|
+
#
|
118
|
+
# steem = Steem.new(account_name: 'your account name', wif: 'your wif')
|
119
|
+
# steem.vote!(10000, 'author', 'permlink')
|
120
|
+
#
|
121
|
+
# ... or ...
|
122
|
+
#
|
123
|
+
# steem = Steem.new(account_name: 'your account name', wif: 'your wif')
|
124
|
+
# steem.vote!(10000, '@author/permlink')
|
125
|
+
#
|
126
|
+
# @see vote
|
127
|
+
def vote!(weight, *args); vote(weight, *args).broadcast!(true); end
|
128
|
+
|
129
|
+
# Creates a post operation.
|
130
|
+
#
|
131
|
+
# steem = Steem.new(account_name: 'your account name', wif: 'your wif')
|
132
|
+
# options = {
|
133
|
+
# title: 'This is my fancy post title.',
|
134
|
+
# body: 'This is my fancy post body.',
|
135
|
+
# tags: %w(thess are my fancy tags)
|
136
|
+
# }
|
137
|
+
# steem.post(options)
|
138
|
+
# steem.broadcast!
|
139
|
+
#
|
140
|
+
# @param options [Hash] options
|
141
|
+
# @option options [String] :title Title of the post.
|
142
|
+
# @option options [String] :body Body of the post.
|
143
|
+
# @option options [Array<String>] :tags Tags of the post.
|
144
|
+
# @option options [String] :permlink (automatic) Permlink of the post, defaults to formatted title.
|
145
|
+
# @option options [String] :parent_permlink (automatic) Parent permlink of the post, defaults to first tag.
|
146
|
+
# @option options [String] :parent_author (optional) Parent author of the post (only used if reply).
|
147
|
+
# @option options [String] :max_accepted_payout (1000000.000 SBD) Maximum accepted payout, set to '0.000 SBD' to deline payout
|
148
|
+
# @option options [Integer] :percent_steem_dollars (5000) Percent STEEM Dollars is used to set 50/50 or 100% STEEM Power
|
149
|
+
# @option options [Integer] :allow_votes (true) Allow votes for this post.
|
150
|
+
# @option options [Integer] :allow_curation_rewards (true) Allow curation rewards for this post.
|
151
|
+
def post(options = {})
|
152
|
+
tags = [options[:tags] || []].flatten
|
153
|
+
title = options[:title].to_s
|
154
|
+
permlink = options[:permlink] || title.downcase.gsub(/[^a-z0-9\-]+/, '-')
|
155
|
+
parent_permlink = options[:parent_permlink] || tags[0]
|
156
|
+
|
157
|
+
raise ChainError, 'At least one tag is required or set the parent_permlink directy.' if parent_permlink.nil?
|
158
|
+
|
159
|
+
body = options[:body]
|
160
|
+
parent_author = options[:parent_author] || ''
|
161
|
+
max_accepted_payout = options[:max_accepted_payout] || default_max_acepted_payout
|
162
|
+
percent_steem_dollars = options[:percent_steem_dollars]
|
163
|
+
allow_votes = options[:allow_votes] || true
|
164
|
+
allow_curation_rewards = options[:allow_curation_rewards] || true
|
165
|
+
self_vote = options[:self_vote]
|
166
|
+
|
167
|
+
tags.insert(0, parent_permlink)
|
168
|
+
tags = tags.compact.uniq
|
169
|
+
|
170
|
+
metadata = {
|
171
|
+
app: Radiator::AGENT_ID
|
172
|
+
}
|
173
|
+
metadata[:tags] = tags if tags.any?
|
174
|
+
|
175
|
+
@operations << {
|
176
|
+
type: :comment,
|
177
|
+
parent_permlink: parent_permlink,
|
178
|
+
author: account_name,
|
179
|
+
permlink: permlink,
|
180
|
+
title: title,
|
181
|
+
body: body,
|
182
|
+
json_metadata: metadata.to_json,
|
183
|
+
parent_author: parent_author
|
184
|
+
}
|
185
|
+
|
186
|
+
if (!!max_accepted_payout &&
|
187
|
+
max_accepted_payout != default_max_acepted_payout
|
188
|
+
) || !!percent_steem_dollars || !allow_votes || !allow_curation_rewards
|
189
|
+
@operations << {
|
190
|
+
type: :comment_options,
|
191
|
+
author: account_name,
|
192
|
+
permlink: permlink,
|
193
|
+
max_accepted_payout: max_accepted_payout,
|
194
|
+
percent_steem_dollars: percent_steem_dollars,
|
195
|
+
allow_votes: allow_votes,
|
196
|
+
allow_curation_rewards: allow_curation_rewards,
|
197
|
+
extensions: []
|
198
|
+
}
|
199
|
+
end
|
200
|
+
|
201
|
+
vote(self_vote, account_name, permlink) if !!self_vote
|
202
|
+
|
203
|
+
self
|
204
|
+
end
|
205
|
+
|
206
|
+
# Create a vote operation and broadcasts it right away.
|
207
|
+
#
|
208
|
+
# steem = Steem.new(account_name: 'your account name', wif: 'your wif')
|
209
|
+
# options = {
|
210
|
+
# title: 'This is my fancy post title.',
|
211
|
+
# body: 'This is my fancy post body.',
|
212
|
+
# tags: %w(thess are my fancy tags)
|
213
|
+
# }
|
214
|
+
# steem.post!(options)
|
215
|
+
#
|
216
|
+
# @see post
|
217
|
+
def post!(options = {}); post(options).broadcast!(true); end
|
218
|
+
|
219
|
+
# Create a delete_comment operation.
|
220
|
+
#
|
221
|
+
# Examples:
|
222
|
+
#
|
223
|
+
# steem = Steem.new(account_name: 'your account name', wif: 'your wif')
|
224
|
+
# steem.delete_comment('permlink')
|
225
|
+
# steem.broadcast!
|
226
|
+
#
|
227
|
+
# @param permlink
|
228
|
+
def delete_comment(permlink)
|
229
|
+
@operations << {
|
230
|
+
type: :delete_comment,
|
231
|
+
author: account_name,
|
232
|
+
permlink: permlink
|
233
|
+
}
|
234
|
+
|
235
|
+
self
|
236
|
+
end
|
237
|
+
|
238
|
+
# Create a delete_comment operation and broadcasts it right away.
|
239
|
+
#
|
240
|
+
# Examples:
|
241
|
+
#
|
242
|
+
# steem = Steem.new(account_name: 'your account name', wif: 'your wif')
|
243
|
+
# steem.delete_comment!('permlink')
|
244
|
+
#
|
245
|
+
# @see delete_comment
|
246
|
+
def delete_comment!(permlink); delete_comment(permlink).broadcast!(true); end
|
247
|
+
|
248
|
+
# Create a claim_reward_balance operation.
|
249
|
+
#
|
250
|
+
# Examples:
|
251
|
+
#
|
252
|
+
# steem = Steem.new(account_name: 'your account name', wif: 'your wif')
|
253
|
+
# steem.claim_reward_balance(reward_sbd: '100.000 SBD')
|
254
|
+
# steem.broadcast!
|
255
|
+
#
|
256
|
+
# @param options [Hash] options
|
257
|
+
# @option options [String] :reward_steem The amount of STEEM to claim, like: `100.000 STEEM`
|
258
|
+
# @option options [String] :reward_sbd The amount of SBD to claim, like: `100.000 SBD`
|
259
|
+
# @option options [String] :reward_vests The amount of VESTS to claim, like: `100.000000 VESTS`
|
260
|
+
def claim_reward_balance(options)
|
261
|
+
reward_steem = options[:reward_steem] || '0.000 STEEM'
|
262
|
+
reward_sbd = options[:reward_sbd] || '0.000 SBD'
|
263
|
+
reward_vests = options[:reward_vests] || '0.000000 VESTS'
|
264
|
+
|
265
|
+
@operations << {
|
266
|
+
type: :claim_reward_balance,
|
267
|
+
account: account_name,
|
268
|
+
reward_steem: reward_steem,
|
269
|
+
reward_sbd: reward_sbd,
|
270
|
+
reward_vests: reward_vests
|
271
|
+
}
|
272
|
+
|
273
|
+
self
|
274
|
+
end
|
275
|
+
|
276
|
+
# Create a claim_reward_balance operation and broadcasts it right away.
|
277
|
+
#
|
278
|
+
# Examples:
|
279
|
+
#
|
280
|
+
# steem = Steem.new(account_name: 'your account name', wif: 'your wif')
|
281
|
+
# steem.claim_reward_balance!(reward_sbd: '100.000 SBD')
|
282
|
+
#
|
283
|
+
# @see claim_reward_balance
|
284
|
+
def claim_reward_balance!(permlink); claim_reward_balance(permlink).broadcast!(true); end
|
285
|
+
|
286
|
+
# Create a transfer operation.
|
287
|
+
#
|
288
|
+
# steem = Steem.new(account_name: 'your account name', wif: 'your active wif')
|
289
|
+
# steem.transfer(amount: '1.000 SBD', to: 'account name', memo: 'this is a memo')
|
290
|
+
# steem.broadcast!
|
291
|
+
#
|
292
|
+
# @param options [Hash] options
|
293
|
+
# @option options [String] :amount The amount to transfer, like: `100.000 STEEM`
|
294
|
+
# @option options [String] :to The account receiving the transfer.
|
295
|
+
# @option options [String] :memo ('') The memo for the transfer.
|
296
|
+
def transfer(options = {})
|
297
|
+
@operations << options.merge(type: :transfer, from: account_name)
|
298
|
+
|
299
|
+
self
|
300
|
+
end
|
301
|
+
|
302
|
+
# Create a transfer operation and broadcasts it right away.
|
303
|
+
#
|
304
|
+
# steem = Steem.new(account_name: 'your account name', wif: 'your wif')
|
305
|
+
# steem.transfer!(amount: '1.000 SBD', to: 'account name', memo: 'this is a memo')
|
306
|
+
#
|
307
|
+
# @see transfer
|
308
|
+
def transfer!(options = {}); transfer(options).broadcast!(true); end
|
309
|
+
private
|
310
|
+
def build_options
|
311
|
+
{
|
312
|
+
chain: chain,
|
313
|
+
wif: wif
|
314
|
+
}
|
315
|
+
end
|
316
|
+
|
317
|
+
def parse_slug(slug)
|
318
|
+
slug = slug.split('@').last
|
319
|
+
author = slug.split('/')[0]
|
320
|
+
[author, slug.split('/')[1..-1].join('/')]
|
321
|
+
end
|
322
|
+
|
323
|
+
def default_max_acepted_payout
|
324
|
+
"1000000.000 #{default_debt_asset}"
|
325
|
+
end
|
326
|
+
|
327
|
+
def default_debt_asset
|
328
|
+
case chain
|
329
|
+
when :steem then 'SBD'
|
330
|
+
when :golos then 'GBG'
|
331
|
+
else; raise ChainError, "Unknown chain: #{chain}"
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end
|
@@ -1,8 +1,10 @@
|
|
1
|
+
require 'awesome_print'
|
2
|
+
|
1
3
|
module Radiator
|
2
4
|
class ErrorParser
|
3
5
|
include Utils
|
4
6
|
|
5
|
-
attr_reader :response, :error_code, :error_message,
|
7
|
+
attr_reader :response, :error, :error_code, :error_message,
|
6
8
|
:api_name, :api_method, :api_params,
|
7
9
|
:expiry, :can_retry, :can_reprepare, :trx_id, :debug
|
8
10
|
|
@@ -22,6 +24,7 @@ module Radiator
|
|
22
24
|
def initialize(response)
|
23
25
|
@response = response
|
24
26
|
|
27
|
+
@error = nil
|
25
28
|
@error_code = nil
|
26
29
|
@error_message = nil
|
27
30
|
@api_name = nil
|
@@ -38,53 +41,85 @@ module Radiator
|
|
38
41
|
end
|
39
42
|
|
40
43
|
def parse_error_response
|
41
|
-
|
42
|
-
|
43
|
-
@error_code = response['error']['data']['code']
|
44
|
-
stacks = response['error']['data']['stack']
|
45
|
-
stack_formats = stacks.map { |s| s['format'] }
|
46
|
-
stack_datum = stacks.map { |s| s['data'] }
|
47
|
-
data_call_method = stack_datum.find { |data| data['call.method'] == 'call' }
|
48
|
-
|
49
|
-
@error_message = stack_formats.reject(&:empty?).join('; ')
|
50
|
-
|
51
|
-
@api_name, @api_method, @api_params = if !!data_call_method
|
52
|
-
@api_name = data_call_method['call.params']
|
53
|
-
end
|
54
|
-
|
55
|
-
# See if we can recover a transaction id out of this hot mess.
|
56
|
-
data_trx_ix = stack_datum.find { |data| !!data['trx_ix'] }
|
57
|
-
@trx_id = data_trx_ix['trx_ix'] if !!data_trx_ix
|
58
|
-
|
59
|
-
case @error_code
|
60
|
-
when 10
|
44
|
+
if response.nil?
|
61
45
|
@expiry = false
|
62
46
|
@can_retry = false
|
63
|
-
@can_reprepare =
|
64
|
-
|
65
|
-
|
66
|
-
|
47
|
+
@can_reprepare = false
|
48
|
+
|
49
|
+
return
|
50
|
+
end
|
51
|
+
|
52
|
+
@response = JSON[response] if response.class == String
|
53
|
+
|
54
|
+
@error = if !!@response['error']
|
55
|
+
response['error']
|
56
|
+
else
|
57
|
+
response
|
58
|
+
end
|
59
|
+
|
60
|
+
begin
|
61
|
+
@error_code = @error['data']['code']
|
62
|
+
stacks = @error['data']['stack']
|
63
|
+
stack_formats = stacks.map { |s| s['format'] }
|
64
|
+
stack_datum = stacks.map { |s| s['data'] }
|
65
|
+
data_call_method = stack_datum.find { |data| data['call.method'] == 'call' }
|
66
|
+
|
67
|
+
@error_message = stack_formats.reject(&:empty?).join('; ')
|
68
|
+
|
69
|
+
@api_name, @api_method, @api_params = if !!data_call_method
|
70
|
+
@api_name = data_call_method['call.params']
|
67
71
|
end
|
68
|
-
when 4030100
|
69
|
-
# Code 4030100 is "transaction_expiration_exception: transaction
|
70
|
-
# expiration exception". If we assume the expiration was valid, the
|
71
|
-
# node might be bad and needs to be dropped.
|
72
72
|
|
73
|
-
|
74
|
-
|
75
|
-
@
|
76
|
-
when 4030200
|
77
|
-
# Code 4030200 is "transaction tapos exception". They are recoverable
|
78
|
-
# if the transaction hasn't expired yet. A tapos exception can be
|
79
|
-
# retried in situations where the node is behind and the tapos is
|
80
|
-
# based on a block the node doesn't know about yet.
|
73
|
+
# See if we can recover a transaction id out of this hot mess.
|
74
|
+
data_trx_ix = stack_datum.find { |data| !!data['trx_ix'] }
|
75
|
+
@trx_id = data_trx_ix['trx_ix'] if !!data_trx_ix
|
81
76
|
|
82
|
-
@
|
83
|
-
|
77
|
+
case @error_code
|
78
|
+
when 10
|
79
|
+
@expiry = false
|
80
|
+
@can_retry = false
|
81
|
+
@can_reprepare = if @api_name == 'network_broadcast_api'
|
82
|
+
(stack_formats & REPREPARE_WHITELIST).any?
|
83
|
+
else
|
84
|
+
false
|
85
|
+
end
|
86
|
+
when 13
|
87
|
+
@error_message = @error['data']['message']
|
88
|
+
@expiry = false
|
89
|
+
@can_retry = false
|
90
|
+
@can_reprepare = false
|
91
|
+
when 3030000
|
92
|
+
@error_message = @error['data']['message']
|
93
|
+
@expiry = false
|
94
|
+
@can_retry = false
|
95
|
+
@can_reprepare = false
|
96
|
+
when 4030100
|
97
|
+
# Code 4030100 is "transaction_expiration_exception: transaction
|
98
|
+
# expiration exception". If we assume the expiration was valid, the
|
99
|
+
# node might be bad and needs to be dropped.
|
100
|
+
|
101
|
+
@expiry = true
|
102
|
+
@can_retry = true
|
103
|
+
@can_reprepare = false
|
104
|
+
when 4030200
|
105
|
+
# Code 4030200 is "transaction tapos exception". They are recoverable
|
106
|
+
# if the transaction hasn't expired yet. A tapos exception can be
|
107
|
+
# retried in situations where the node is behind and the tapos is
|
108
|
+
# based on a block the node doesn't know about yet.
|
109
|
+
|
110
|
+
@expiry = false
|
111
|
+
@can_retry = true
|
112
|
+
|
113
|
+
# Allow fall back to reprepare if retry fails.
|
114
|
+
@can_reprepare = true
|
115
|
+
else
|
116
|
+
@expiry = false
|
117
|
+
@can_retry = false
|
118
|
+
@can_reprepare = false
|
119
|
+
end
|
120
|
+
rescue => e
|
121
|
+
ap error_perser_exception: e, original_response: response
|
84
122
|
|
85
|
-
# Allow fall back to reprepare if retry fails.
|
86
|
-
@can_reprepare = true
|
87
|
-
else
|
88
123
|
@expiry = false
|
89
124
|
@can_retry = false
|
90
125
|
@can_reprepare = false
|
@@ -98,5 +133,9 @@ module Radiator
|
|
98
133
|
error_code.to_s
|
99
134
|
end
|
100
135
|
end
|
136
|
+
|
137
|
+
def inspect
|
138
|
+
"#<#{self.class.name} [#{to_s}]>"
|
139
|
+
end
|
101
140
|
end
|
102
141
|
end
|
data/lib/radiator/stream.rb
CHANGED
@@ -23,6 +23,8 @@ module Radiator
|
|
23
23
|
# @private
|
24
24
|
MAX_BLOCKS_PER_NODE = 1000
|
25
25
|
|
26
|
+
RANGE_BEHIND_WARNING = 400
|
27
|
+
|
26
28
|
def initialize(options = {})
|
27
29
|
super
|
28
30
|
end
|
@@ -212,17 +214,24 @@ module Radiator
|
|
212
214
|
# @param mode we have the choice between
|
213
215
|
# * :head the last block
|
214
216
|
# * :irreversible the block that is confirmed by 2/3 of all block producers and is thus irreversible!
|
215
|
-
#
|
217
|
+
# @param max_blocks_per_node the number of blocks to read before trying a new node
|
216
218
|
# @param block the block to execute for each result, optional.
|
217
219
|
# @return [Hash]
|
218
220
|
def blocks(start = nil, mode = :irreversible, max_blocks_per_node = MAX_BLOCKS_PER_NODE, &block)
|
221
|
+
reset_api
|
222
|
+
|
223
|
+
replay = !!start
|
219
224
|
counter = 0
|
220
225
|
latest_block_number = -1
|
221
226
|
@api_options[:max_requests] = [max_blocks_per_node * 2, @api_options[:max_requests].to_i].max
|
222
227
|
|
223
228
|
loop do
|
229
|
+
break if stop?
|
230
|
+
|
224
231
|
catch :sequence do; begin
|
225
232
|
head_block = api.get_dynamic_global_properties do |properties|
|
233
|
+
break if stop?
|
234
|
+
|
226
235
|
if properties.head_block_number.nil?
|
227
236
|
# This can happen if a reverse proxy is acting up.
|
228
237
|
standby "Bad block sequence after height: #{latest_block_number}", {
|
@@ -251,18 +260,27 @@ module Radiator
|
|
251
260
|
start ||= head_block
|
252
261
|
range = (start..head_block)
|
253
262
|
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
standby "Stream behind by #{range.size} blocks (about #{(range.size * 3) / 60.0} minutes)."
|
258
|
-
end
|
259
|
-
|
260
|
-
[*range].each do |n|
|
263
|
+
for n in range
|
264
|
+
break if stop?
|
265
|
+
|
261
266
|
if (counter += 1) > max_blocks_per_node
|
262
|
-
|
267
|
+
reset_api
|
263
268
|
counter = 0
|
264
269
|
end
|
265
|
-
|
270
|
+
|
271
|
+
if !replay && range.size > RANGE_BEHIND_WARNING
|
272
|
+
# When the range is above RANGE_BEHIND_WARNING blocks, it's time
|
273
|
+
# to warn, unless we're replaying.
|
274
|
+
|
275
|
+
r = [*range]
|
276
|
+
index = r.index(n)
|
277
|
+
current_range = r[index..-1]
|
278
|
+
|
279
|
+
if current_range.size % RANGE_BEHIND_WARNING == 0
|
280
|
+
standby "Stream behind by #{current_range.size} blocks (about #{(current_range.size * 3) / 60.0} minutes)."
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
266
284
|
block_api.get_block(n) do |current_block, error|
|
267
285
|
if current_block.nil?
|
268
286
|
standby "Node responded with: empty block, retrying ...", {
|
@@ -295,13 +313,24 @@ module Radiator
|
|
295
313
|
# Stops the persistant http connections.
|
296
314
|
#
|
297
315
|
def shutdown
|
316
|
+
flappy = false
|
317
|
+
|
298
318
|
begin
|
299
|
-
@api.
|
319
|
+
unless @api.nil?
|
320
|
+
flappy = @api.send(:flappy?)
|
321
|
+
@api.shutdown
|
322
|
+
end
|
323
|
+
|
324
|
+
unless @block_api.nil?
|
325
|
+
flappy = @block_api.send(:flappy?) unless flappy
|
326
|
+
@block_api.shutdown
|
327
|
+
end
|
300
328
|
rescue => e
|
301
329
|
warning("Unable to shut down: #{e}")
|
302
330
|
end
|
303
331
|
|
304
332
|
@api = nil
|
333
|
+
@block_api = nil
|
305
334
|
end
|
306
335
|
|
307
336
|
# @private
|
@@ -353,6 +382,8 @@ module Radiator
|
|
353
382
|
@latest_values ||= []
|
354
383
|
@latest_values.shift(5) if @latest_values.size > 20
|
355
384
|
loop do
|
385
|
+
break if stop?
|
386
|
+
|
356
387
|
value = if (n = method_params(m)).nil?
|
357
388
|
key_value = api.get_dynamic_global_properties.result[m]
|
358
389
|
else
|
@@ -362,12 +393,14 @@ module Radiator
|
|
362
393
|
key_value = param = r[n[key]]
|
363
394
|
result = nil
|
364
395
|
loop do
|
396
|
+
break if stop?
|
397
|
+
|
365
398
|
response = api.send(key, param)
|
366
399
|
raise StreamError, JSON[response.error] if !!response.error
|
367
400
|
result = response.result
|
368
401
|
break if !!result
|
369
402
|
warnning "#{key}: #{param} result missing, retrying with timeout: #{@timeout || INITIAL_TIMEOUT} seconds"
|
370
|
-
|
403
|
+
reset_api
|
371
404
|
sleep timeout
|
372
405
|
end
|
373
406
|
@timeout = INITIAL_TIMEOUT
|
@@ -388,6 +421,11 @@ module Radiator
|
|
388
421
|
end
|
389
422
|
end
|
390
423
|
|
424
|
+
def reset_api
|
425
|
+
shutdown
|
426
|
+
!!api && !!block_api
|
427
|
+
end
|
428
|
+
|
391
429
|
def timeout
|
392
430
|
@timeout ||= INITIAL_TIMEOUT
|
393
431
|
@timeout *= 2
|
@@ -401,6 +439,10 @@ module Radiator
|
|
401
439
|
(Radiator::OperationTypes::TYPES.keys && type).any?
|
402
440
|
end
|
403
441
|
|
442
|
+
def stop?
|
443
|
+
@api.nil? || @block_api.nil?
|
444
|
+
end
|
445
|
+
|
404
446
|
def standby(message, options = {})
|
405
447
|
error = options[:error]
|
406
448
|
secondary = options[:and] || {}
|
data/lib/radiator/transaction.rb
CHANGED
@@ -164,7 +164,7 @@ module Radiator
|
|
164
164
|
if block.nil?
|
165
165
|
warning "Block missing while trying to prepare transaction, retrying ..."
|
166
166
|
else
|
167
|
-
debug block if ENV['LOG']
|
167
|
+
debug block if %w(DEBUG TRACE).include? ENV['LOG']
|
168
168
|
|
169
169
|
warning "Block structure while trying to prepare transaction, retrying ..."
|
170
170
|
end
|
@@ -197,14 +197,17 @@ module Radiator
|
|
197
197
|
Digest::SHA256.digest(to_bytes)
|
198
198
|
end
|
199
199
|
|
200
|
+
# May not find all non-canonicals, see: https://github.com/lian/bitcoin-ruby/issues/196
|
200
201
|
def signature
|
201
202
|
public_key_hex = @private_key.pub
|
202
203
|
ec = Bitcoin::OpenSSL_EC
|
203
204
|
digest_hex = digest.freeze
|
205
|
+
count = 0
|
204
206
|
|
205
207
|
loop do
|
206
|
-
|
207
|
-
|
208
|
+
count += 1
|
209
|
+
debug "#{count} attempts to find canonical signature" if count % 40 == 0
|
210
|
+
sig = ec.sign_compact(digest_hex, @private_key.priv, public_key_hex, false)
|
208
211
|
|
209
212
|
next if public_key_hex != ec.recover_compact(digest_hex, sig)
|
210
213
|
|
data/lib/radiator/utils.rb
CHANGED
data/lib/radiator/version.rb
CHANGED
data/lib/radiator.rb
CHANGED
data/lib/steem.rb
ADDED
data/radiator.gemspec
CHANGED
@@ -27,11 +27,13 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.add_development_dependency 'vcr', '~> 3.0', '>= 3.0.3'
|
28
28
|
spec.add_development_dependency 'yard', '~> 0.9.9'
|
29
29
|
|
30
|
-
|
30
|
+
# net-http-persistent has an open-ended dependency because radiator directly
|
31
|
+
# supports net-http-persistent-3.0.0 as well as net-http-persistent-2.5.2.
|
32
|
+
spec.add_dependency('net-http-persistent', '>= 2.5.2')
|
31
33
|
spec.add_dependency('json', '~> 2.0', '>= 2.0.2')
|
32
34
|
spec.add_dependency('logging', '~> 2.2', '>= 2.2.0')
|
33
35
|
spec.add_dependency('hashie', '~> 3.5', '>= 3.5.5')
|
34
36
|
spec.add_dependency('bitcoin-ruby', '~> 0.0', '>= 0.0.11')
|
35
37
|
spec.add_dependency('ffi', '~> 1.9', '>= 1.9.18')
|
36
|
-
spec.add_dependency
|
38
|
+
spec.add_dependency('awesome_print', '~> 1.7', '>= 1.7.0')
|
37
39
|
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.
|
4
|
+
version: 0.3.10
|
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-
|
11
|
+
date: 2017-11-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -176,9 +176,6 @@ dependencies:
|
|
176
176
|
name: net-http-persistent
|
177
177
|
requirement: !ruby/object:Gem::Requirement
|
178
178
|
requirements:
|
179
|
-
- - "~>"
|
180
|
-
- !ruby/object:Gem::Version
|
181
|
-
version: '3.0'
|
182
179
|
- - ">="
|
183
180
|
- !ruby/object:Gem::Version
|
184
181
|
version: 2.5.2
|
@@ -186,9 +183,6 @@ dependencies:
|
|
186
183
|
prerelease: false
|
187
184
|
version_requirements: !ruby/object:Gem::Requirement
|
188
185
|
requirements:
|
189
|
-
- - "~>"
|
190
|
-
- !ruby/object:Gem::Version
|
191
|
-
version: '3.0'
|
192
186
|
- - ">="
|
193
187
|
- !ruby/object:Gem::Version
|
194
188
|
version: 2.5.2
|
@@ -331,12 +325,14 @@ files:
|
|
331
325
|
- images/Anthony Martin.png
|
332
326
|
- images/Marvin Hofmann.jpg
|
333
327
|
- images/Marvin Hofmann.png
|
328
|
+
- lib/golos.rb
|
334
329
|
- lib/radiator.rb
|
335
330
|
- lib/radiator/account_by_key_api.rb
|
336
331
|
- lib/radiator/api.rb
|
337
332
|
- lib/radiator/base_error.rb
|
338
333
|
- lib/radiator/block_api.rb
|
339
334
|
- lib/radiator/broadcast_operations.json
|
335
|
+
- lib/radiator/chain.rb
|
340
336
|
- lib/radiator/chain_config.rb
|
341
337
|
- lib/radiator/chain_stats_api.rb
|
342
338
|
- lib/radiator/condenser_api.rb
|
@@ -365,6 +361,7 @@ files:
|
|
365
361
|
- lib/radiator/type/u_int32.rb
|
366
362
|
- lib/radiator/utils.rb
|
367
363
|
- lib/radiator/version.rb
|
364
|
+
- lib/steem.rb
|
368
365
|
- radiator.gemspec
|
369
366
|
homepage: https://github.com/inertia186/radiator
|
370
367
|
licenses:
|