radiator 0.3.15 → 0.4.0pre1
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 +9 -9
- data/README.md +2 -18
- data/Rakefile +11 -2
- data/lib/golos.rb +3 -0
- data/lib/radiator/api.rb +62 -123
- data/lib/radiator/chain.rb +184 -224
- data/lib/radiator/error_parser.rb +3 -5
- data/lib/radiator/logger.rb +4 -2
- data/lib/radiator/mixins/acts_as_poster.rb +124 -0
- data/lib/radiator/mixins/acts_as_voter.rb +50 -0
- data/lib/radiator/mixins/acts_as_wallet.rb +67 -0
- data/lib/radiator/transaction.rb +64 -51
- data/lib/radiator/type/permission.rb +2 -1
- data/lib/radiator/utils.rb +3 -11
- data/lib/radiator/version.rb +1 -1
- data/lib/radiator.rb +3 -1
- data/lib/steem.rb +3 -0
- metadata +8 -5
@@ -0,0 +1,50 @@
|
|
1
|
+
module Radiator
|
2
|
+
module Mixins
|
3
|
+
module ActsAsVoter
|
4
|
+
# Create a vote operation.
|
5
|
+
#
|
6
|
+
# Examples:
|
7
|
+
#
|
8
|
+
# steem = Steem.new(account_name: 'your account name', wif: 'your wif')
|
9
|
+
# steem.vote(10000, 'author', 'permlink')
|
10
|
+
# steem.broadcast!
|
11
|
+
#
|
12
|
+
# ... or ...
|
13
|
+
#
|
14
|
+
# steem = Steem.new(account_name: 'your account name', wif: 'your wif')
|
15
|
+
# steem.vote(10000, '@author/permlink')
|
16
|
+
# steem.broadcast!
|
17
|
+
#
|
18
|
+
# @param weight [Integer] value between -10000 and 10000.
|
19
|
+
# @param args [author, permlink || slug] pass either `author` and `permlink` or string containing both like `@author/permlink`.
|
20
|
+
def vote(weight, *args)
|
21
|
+
author, permlink = normalize_author_permlink(args)
|
22
|
+
|
23
|
+
@operations << {
|
24
|
+
type: :vote,
|
25
|
+
voter: account_name,
|
26
|
+
author: author,
|
27
|
+
permlink: permlink,
|
28
|
+
weight: weight
|
29
|
+
}
|
30
|
+
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
# Create a vote operation and broadcasts it right away.
|
35
|
+
#
|
36
|
+
# Examples:
|
37
|
+
#
|
38
|
+
# steem = Steem.new(account_name: 'your account name', wif: 'your wif')
|
39
|
+
# steem.vote!(10000, 'author', 'permlink')
|
40
|
+
#
|
41
|
+
# ... or ...
|
42
|
+
#
|
43
|
+
# steem = Steem.new(account_name: 'your account name', wif: 'your wif')
|
44
|
+
# steem.vote!(10000, '@author/permlink')
|
45
|
+
#
|
46
|
+
# @see vote
|
47
|
+
def vote!(weight, *args); vote(weight, *args).broadcast!(true); end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Radiator
|
2
|
+
module Mixins
|
3
|
+
module ActsAsWallet
|
4
|
+
# Create a claim_reward_balance operation.
|
5
|
+
#
|
6
|
+
# Examples:
|
7
|
+
#
|
8
|
+
# steem = Steem.new(account_name: 'your account name', wif: 'your wif')
|
9
|
+
# steem.claim_reward_balance(reward_sbd: '100.000 SBD')
|
10
|
+
# steem.broadcast!
|
11
|
+
#
|
12
|
+
# @param options [Hash] options
|
13
|
+
# @option options [String] :reward_steem The amount of STEEM to claim, like: `100.000 STEEM`
|
14
|
+
# @option options [String] :reward_sbd The amount of SBD to claim, like: `100.000 SBD`
|
15
|
+
# @option options [String] :reward_vests The amount of VESTS to claim, like: `100.000000 VESTS`
|
16
|
+
def claim_reward_balance(options)
|
17
|
+
reward_steem = options[:reward_steem] || '0.000 STEEM'
|
18
|
+
reward_sbd = options[:reward_sbd] || '0.000 SBD'
|
19
|
+
reward_vests = options[:reward_vests] || '0.000000 VESTS'
|
20
|
+
|
21
|
+
@operations << {
|
22
|
+
type: :claim_reward_balance,
|
23
|
+
account: account_name,
|
24
|
+
reward_steem: reward_steem,
|
25
|
+
reward_sbd: reward_sbd,
|
26
|
+
reward_vests: reward_vests
|
27
|
+
}
|
28
|
+
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
# Create a claim_reward_balance operation and broadcasts it right away.
|
33
|
+
#
|
34
|
+
# Examples:
|
35
|
+
#
|
36
|
+
# steem = Steem.new(account_name: 'your account name', wif: 'your wif')
|
37
|
+
# steem.claim_reward_balance!(reward_sbd: '100.000 SBD')
|
38
|
+
#
|
39
|
+
# @see claim_reward_balance
|
40
|
+
def claim_reward_balance!(permlink); claim_reward_balance(permlink).broadcast!(true); end
|
41
|
+
|
42
|
+
# Create a transfer operation.
|
43
|
+
#
|
44
|
+
# steem = Steem.new(account_name: 'your account name', wif: 'your active wif')
|
45
|
+
# steem.transfer(amount: '1.000 SBD', to: 'account name', memo: 'this is a memo')
|
46
|
+
# steem.broadcast!
|
47
|
+
#
|
48
|
+
# @param options [Hash] options
|
49
|
+
# @option options [String] :amount The amount to transfer, like: `100.000 STEEM`
|
50
|
+
# @option options [String] :to The account receiving the transfer.
|
51
|
+
# @option options [String] :memo ('') The memo for the transfer.
|
52
|
+
def transfer(options = {})
|
53
|
+
@operations << options.merge(type: :transfer, from: account_name)
|
54
|
+
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
# Create a transfer operation and broadcasts it right away.
|
59
|
+
#
|
60
|
+
# steem = Steem.new(account_name: 'your account name', wif: 'your wif')
|
61
|
+
# steem.transfer!(amount: '1.000 SBD', to: 'account name', memo: 'this is a memo')
|
62
|
+
#
|
63
|
+
# @see transfer
|
64
|
+
def transfer!(options = {}); transfer(options).broadcast!(true); end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/radiator/transaction.rb
CHANGED
@@ -8,13 +8,13 @@ module Radiator
|
|
8
8
|
class Transaction
|
9
9
|
include ChainConfig
|
10
10
|
include Utils
|
11
|
-
|
11
|
+
|
12
12
|
VALID_OPTIONS = %w(
|
13
13
|
wif private_key ref_block_num ref_block_prefix expiration
|
14
14
|
chain
|
15
15
|
).map(&:to_sym)
|
16
16
|
VALID_OPTIONS.each { |option| attr_accessor option }
|
17
|
-
|
17
|
+
|
18
18
|
def initialize(options = {})
|
19
19
|
options = options.dup
|
20
20
|
options.each do |k, v|
|
@@ -24,29 +24,31 @@ module Radiator
|
|
24
24
|
send("#{k}=", v)
|
25
25
|
end
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
@logger = options[:logger] || Radiator.logger
|
29
29
|
@chain ||= :steem
|
30
30
|
@chain = @chain.to_sym
|
31
31
|
@chain_id = chain_id options[:chain_id]
|
32
32
|
@url = options[:url] || url
|
33
33
|
@operations = options[:operations] || []
|
34
|
-
|
34
|
+
|
35
35
|
unless NETWORK_CHAIN_IDS.include? @chain_id
|
36
36
|
warning "Unknown chain id: #{@chain_id}"
|
37
37
|
end
|
38
|
-
|
38
|
+
|
39
39
|
if !!wif && !!private_key
|
40
40
|
raise TransactionError, "Do not pass both wif and private_key. That's confusing."
|
41
41
|
end
|
42
|
-
|
42
|
+
|
43
43
|
if !!wif
|
44
44
|
@private_key = Bitcoin::Key.from_base58 wif
|
45
45
|
end
|
46
|
-
|
46
|
+
|
47
|
+
@ref_block_num ||= nil
|
48
|
+
@ref_block_prefix ||= nil
|
47
49
|
@expiration ||= nil
|
48
50
|
@immutable_expiration = !!@expiration
|
49
|
-
|
51
|
+
|
50
52
|
options = options.merge(
|
51
53
|
url: @url,
|
52
54
|
chain: @chain,
|
@@ -54,23 +56,23 @@ module Radiator
|
|
54
56
|
persist: false,
|
55
57
|
reuse_ssl_sessions: false
|
56
58
|
)
|
57
|
-
|
59
|
+
|
58
60
|
@api = Api.new(options)
|
59
61
|
@network_broadcast_api = NetworkBroadcastApi.new(options)
|
60
|
-
|
62
|
+
|
61
63
|
ObjectSpace.define_finalizer(self, self.class.finalize(@api, @network_broadcast_api, @logger))
|
62
64
|
end
|
63
|
-
|
65
|
+
|
64
66
|
def chain_id(chain_id = nil)
|
65
67
|
return chain_id if !!chain_id
|
66
|
-
|
68
|
+
|
67
69
|
case chain.to_s.downcase.to_sym
|
68
70
|
when :steem then NETWORKS_STEEM_CHAIN_ID
|
69
71
|
when :golos then NETWORKS_GOLOS_CHAIN_ID
|
70
72
|
when :test then NETWORKS_TEST_CHAIN_ID
|
71
73
|
end
|
72
74
|
end
|
73
|
-
|
75
|
+
|
74
76
|
def url
|
75
77
|
case chain.to_s.downcase.to_sym
|
76
78
|
when :steem then NETWORKS_STEEM_DEFAULT_NODE
|
@@ -78,24 +80,24 @@ module Radiator
|
|
78
80
|
when :test then NETWORKS_TEST_DEFAULT_NODE
|
79
81
|
end
|
80
82
|
end
|
81
|
-
|
83
|
+
|
82
84
|
def process(broadcast = false)
|
83
85
|
prepare
|
84
|
-
|
86
|
+
|
85
87
|
if broadcast
|
86
88
|
loop do
|
87
89
|
response = @network_broadcast_api.broadcast_transaction_synchronous(payload)
|
88
|
-
|
90
|
+
|
89
91
|
if !!response.error
|
90
92
|
parser = ErrorParser.new(response)
|
91
|
-
|
93
|
+
|
92
94
|
if parser.can_reprepare?
|
93
95
|
debug "Error code: #{parser}, repreparing transaction ..."
|
94
96
|
prepare
|
95
|
-
redo
|
97
|
+
redo
|
96
98
|
end
|
97
99
|
end
|
98
|
-
|
100
|
+
|
99
101
|
return response
|
100
102
|
end
|
101
103
|
else
|
@@ -104,7 +106,7 @@ module Radiator
|
|
104
106
|
ensure
|
105
107
|
shutdown
|
106
108
|
end
|
107
|
-
|
109
|
+
|
108
110
|
def operations
|
109
111
|
@operations = @operations.map do |op|
|
110
112
|
case op
|
@@ -113,14 +115,20 @@ module Radiator
|
|
113
115
|
end
|
114
116
|
end
|
115
117
|
end
|
116
|
-
|
118
|
+
|
117
119
|
def operations=(operations)
|
118
120
|
@operations = operations
|
119
121
|
end
|
120
|
-
|
122
|
+
|
121
123
|
def shutdown
|
122
124
|
@api.shutdown if !!@api
|
123
125
|
@network_broadcast_api.shutdown if !!@network_broadcast_api
|
126
|
+
|
127
|
+
if !!@logger && defined?(@logger.close)
|
128
|
+
if defined?(@logger.closed?)
|
129
|
+
@logger.close unless @logger.closed?
|
130
|
+
end
|
131
|
+
end
|
124
132
|
end
|
125
133
|
private
|
126
134
|
def payload
|
@@ -133,35 +141,35 @@ module Radiator
|
|
133
141
|
signatures: [hexlify(signature)]
|
134
142
|
}
|
135
143
|
end
|
136
|
-
|
144
|
+
|
137
145
|
def prepare
|
138
146
|
raise TransactionError, "No wif or private key." unless !!@wif || !!@private_key
|
139
|
-
|
147
|
+
|
140
148
|
@payload = nil
|
141
|
-
|
149
|
+
|
142
150
|
while @expiration.nil? && @ref_block_num.nil? && @ref_block_prefix.nil?
|
143
151
|
@api.get_dynamic_global_properties do |properties, error|
|
144
152
|
if !!error
|
145
153
|
raise TransactionError, "Unable to prepare transaction.", error
|
146
154
|
end
|
147
|
-
|
155
|
+
|
148
156
|
@properties = properties
|
149
157
|
end
|
150
|
-
|
158
|
+
|
151
159
|
# You can actually go back as far as the TaPoS buffer will allow, which
|
152
160
|
# is something like 50,000 blocks.
|
153
|
-
|
161
|
+
|
154
162
|
block_number = @properties.last_irreversible_block_num
|
155
|
-
|
163
|
+
|
156
164
|
@api.get_block(block_number) do |block, error|
|
157
165
|
if !!error
|
158
166
|
raise TransactionError, "Unable to prepare transaction.", error
|
159
167
|
end
|
160
|
-
|
168
|
+
|
161
169
|
if !!block && !!block.previous
|
162
170
|
@ref_block_num = (block_number - 1) & 0xFFFF
|
163
171
|
@ref_block_prefix = unhexlify(block.previous[8..-1]).unpack('V*')[0]
|
164
|
-
|
172
|
+
|
165
173
|
# The expiration allows for transactions to expire if they are not
|
166
174
|
# included into a block by that time. Always update it to the current
|
167
175
|
# time + EXPIRE_IN_SECS.
|
@@ -169,50 +177,50 @@ module Radiator
|
|
169
177
|
# Note, as of #1215, expiration exactly 'now' will be rejected:
|
170
178
|
# https://github.com/steemit/steem/blob/57451b80d2cf480dcce9b399e48e56aa7af1d818/libraries/chain/database.cpp#L2870
|
171
179
|
# https://github.com/steemit/steem/issues/1215
|
172
|
-
|
180
|
+
|
173
181
|
block_time = Time.parse(@properties.time + 'Z')
|
174
182
|
@expiration ||= block_time + EXPIRE_IN_SECS
|
175
183
|
else
|
176
184
|
# Suspect this happens when there are microforks, but it should be
|
177
185
|
# rare, especially since we're asking for the last irreversible
|
178
186
|
# block.
|
179
|
-
|
187
|
+
|
180
188
|
if block.nil?
|
181
189
|
warning "Block missing while trying to prepare transaction, retrying ..."
|
182
190
|
else
|
183
191
|
debug block if %w(DEBUG TRACE).include? ENV['LOG']
|
184
|
-
|
192
|
+
|
185
193
|
warning "Block structure while trying to prepare transaction, retrying ..."
|
186
194
|
end
|
187
|
-
|
195
|
+
|
188
196
|
@expiration = nil unless @immutable_expiration
|
189
197
|
end
|
190
198
|
end
|
191
199
|
end
|
192
|
-
|
200
|
+
|
193
201
|
self
|
194
202
|
end
|
195
|
-
|
203
|
+
|
196
204
|
def to_bytes
|
197
205
|
bytes = unhexlify(@chain_id)
|
198
206
|
bytes << pakS(@ref_block_num)
|
199
207
|
bytes << pakI(@ref_block_prefix)
|
200
208
|
bytes << pakI(@expiration.to_i)
|
201
209
|
bytes << pakC(operations.size)
|
202
|
-
|
210
|
+
|
203
211
|
operations.each do |op|
|
204
212
|
bytes << op.to_bytes
|
205
213
|
end
|
206
|
-
|
214
|
+
|
207
215
|
bytes << 0x00 # extensions
|
208
|
-
|
216
|
+
|
209
217
|
bytes
|
210
218
|
end
|
211
|
-
|
219
|
+
|
212
220
|
def digest
|
213
221
|
Digest::SHA256.digest(to_bytes)
|
214
222
|
end
|
215
|
-
|
223
|
+
|
216
224
|
# May not find all non-canonicals, see: https://github.com/lian/bitcoin-ruby/issues/196
|
217
225
|
def signature
|
218
226
|
public_key_hex = @private_key.pub
|
@@ -224,16 +232,17 @@ module Radiator
|
|
224
232
|
count += 1
|
225
233
|
debug "#{count} attempts to find canonical signature" if count % 40 == 0
|
226
234
|
sig = ec.sign_compact(digest_hex, @private_key.priv, public_key_hex, false)
|
227
|
-
|
235
|
+
|
228
236
|
next if public_key_hex != ec.recover_compact(digest_hex, sig)
|
229
|
-
|
237
|
+
|
230
238
|
return sig if canonical? sig
|
231
239
|
end
|
232
240
|
end
|
233
241
|
|
242
|
+
# See: https://github.com/steemit/steem/issues/1944
|
234
243
|
def canonical?(sig)
|
235
244
|
sig = sig.unpack('C*')
|
236
|
-
|
245
|
+
|
237
246
|
!(
|
238
247
|
((sig[0] & 0x80 ) != 0) || ( sig[0] == 0 ) ||
|
239
248
|
((sig[1] & 0x80 ) != 0) ||
|
@@ -241,7 +250,7 @@ module Radiator
|
|
241
250
|
((sig[33] & 0x80 ) != 0)
|
242
251
|
)
|
243
252
|
end
|
244
|
-
|
253
|
+
|
245
254
|
def self.finalize(api, network_broadcast_api, logger)
|
246
255
|
proc {
|
247
256
|
if !!api && !api.stopped?
|
@@ -249,16 +258,20 @@ module Radiator
|
|
249
258
|
api.shutdown
|
250
259
|
api = nil
|
251
260
|
end
|
252
|
-
|
261
|
+
|
253
262
|
if !!network_broadcast_api && !network_broadcast_api.stopped?
|
254
263
|
puts "DESTROY: #{network_broadcast_api.inspect}" if ENV['LOG'] == 'TRACE'
|
255
264
|
network_broadcast_api.shutdown
|
256
265
|
network_broadcast_api = nil
|
257
266
|
end
|
258
|
-
|
259
|
-
|
260
|
-
logger.close
|
261
|
-
|
267
|
+
|
268
|
+
begin
|
269
|
+
if !!logger && defined?(logger.close)
|
270
|
+
if defined?(logger.closed?)
|
271
|
+
logger.close unless logger.closed?
|
272
|
+
end
|
273
|
+
end
|
274
|
+
rescue IOError, NoMethodError => _; end
|
262
275
|
}
|
263
276
|
end
|
264
277
|
end
|
data/lib/radiator/utils.rb
CHANGED
@@ -37,18 +37,10 @@ module Radiator
|
|
37
37
|
puts "#{level}: #{log_message}"
|
38
38
|
end
|
39
39
|
else
|
40
|
-
if
|
41
|
-
|
42
|
-
@logger.ap log_level: level, prefix => obj
|
43
|
-
else
|
44
|
-
@logger.ap obj, level
|
45
|
-
end
|
40
|
+
if !!prefix
|
41
|
+
@logger.ap log_level: level, prefix => obj
|
46
42
|
else
|
47
|
-
|
48
|
-
@logger.send level, ({prefix => obj}).inspect
|
49
|
-
else
|
50
|
-
@logger.send level, obj.inspect
|
51
|
-
end
|
43
|
+
@logger.ap obj, level
|
52
44
|
end
|
53
45
|
end
|
54
46
|
|
data/lib/radiator/version.rb
CHANGED
data/lib/radiator.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'radiator/version'
|
2
2
|
require 'json'
|
3
|
-
require 'awesome_print' if ENV['USE_AWESOME_PRINT'] == 'true'
|
4
3
|
|
5
4
|
module Radiator
|
6
5
|
require 'radiator/utils'
|
@@ -33,6 +32,9 @@ module Radiator
|
|
33
32
|
require 'radiator/transaction'
|
34
33
|
require 'radiator/base_error'
|
35
34
|
require 'radiator/error_parser'
|
35
|
+
require 'radiator/mixins/acts_as_poster'
|
36
|
+
require 'radiator/mixins/acts_as_voter'
|
37
|
+
require 'radiator/mixins/acts_as_wallet'
|
36
38
|
require 'radiator/chain'
|
37
39
|
require 'steem'
|
38
40
|
require 'golos'
|
data/lib/steem.rb
CHANGED
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.
|
4
|
+
version: 0.4.0pre1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Anthony Martin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-02-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -342,6 +342,9 @@ files:
|
|
342
342
|
- lib/radiator/logger.rb
|
343
343
|
- lib/radiator/market_history_api.rb
|
344
344
|
- lib/radiator/methods.json
|
345
|
+
- lib/radiator/mixins/acts_as_poster.rb
|
346
|
+
- lib/radiator/mixins/acts_as_voter.rb
|
347
|
+
- lib/radiator/mixins/acts_as_wallet.rb
|
345
348
|
- lib/radiator/network_broadcast_api.rb
|
346
349
|
- lib/radiator/operation.rb
|
347
350
|
- lib/radiator/operation_ids.rb
|
@@ -378,12 +381,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
378
381
|
version: '0'
|
379
382
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
380
383
|
requirements:
|
381
|
-
- - "
|
384
|
+
- - ">"
|
382
385
|
- !ruby/object:Gem::Version
|
383
|
-
version:
|
386
|
+
version: 1.3.1
|
384
387
|
requirements: []
|
385
388
|
rubyforge_project:
|
386
|
-
rubygems_version: 2.6.
|
389
|
+
rubygems_version: 2.6.11
|
387
390
|
signing_key:
|
388
391
|
specification_version: 4
|
389
392
|
summary: STEEM RPC Ruby Client
|