radiator 0.3.15 → 0.4.0pre1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|