steem-ruby 0.9.3 → 0.9.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/Gemfile.lock +9 -5
- data/README.md +10 -10
- data/lib/steem.rb +48 -0
- data/lib/steem/api.rb +0 -21
- data/lib/steem/base_error.rb +5 -3
- data/lib/steem/broadcast.rb +21 -4
- data/lib/steem/marshal.rb +231 -0
- data/lib/steem/mixins/jsonable.rb +37 -0
- data/lib/steem/mixins/serializable.rb +45 -0
- data/lib/steem/operation.rb +141 -0
- data/lib/steem/operation/account_create.rb +10 -0
- data/lib/steem/operation/account_create_with_delegation.rb +12 -0
- data/lib/steem/operation/account_update.rb +8 -0
- data/lib/steem/operation/account_witness_proxy.rb +4 -0
- data/lib/steem/operation/account_witness_vote.rb +5 -0
- data/lib/steem/operation/cancel_transfer_from_savings.rb +4 -0
- data/lib/steem/operation/challenge_authority.rb +5 -0
- data/lib/steem/operation/change_recovery_account.rb +5 -0
- data/lib/steem/operation/claim_account.rb +5 -0
- data/lib/steem/operation/claim_reward_balance.rb +6 -0
- data/lib/steem/operation/comment.rb +9 -0
- data/lib/steem/operation/comment_options.rb +10 -0
- data/lib/steem/operation/convert.rb +5 -0
- data/lib/steem/operation/create_claimed_account.rb +10 -0
- data/lib/steem/operation/custom.rb +5 -0
- data/lib/steem/operation/custom_binary.rb +8 -0
- data/lib/steem/operation/custom_json.rb +6 -0
- data/lib/steem/operation/decline_voting_rights.rb +4 -0
- data/lib/steem/operation/delegate_vesting_shares.rb +5 -0
- data/lib/steem/operation/delete_comment.rb +4 -0
- data/lib/steem/operation/escrow_approve.rb +8 -0
- data/lib/steem/operation/escrow_dispute.rb +7 -0
- data/lib/steem/operation/escrow_release.rb +10 -0
- data/lib/steem/operation/escrow_transfer.rb +12 -0
- data/lib/steem/operation/feed_publish.rb +4 -0
- data/lib/steem/operation/limit_order_cancel.rb +4 -0
- data/lib/steem/operation/limit_order_create.rb +8 -0
- data/lib/steem/operation/limit_order_create2.rb +8 -0
- data/lib/steem/operation/prove_authority.rb +4 -0
- data/lib/steem/operation/recover_account.rb +6 -0
- data/lib/steem/operation/report_over_production.rb +5 -0
- data/lib/steem/operation/request_account_recovery.rb +6 -0
- data/lib/steem/operation/reset_account.rb +5 -0
- data/lib/steem/operation/set_reset_account.rb +5 -0
- data/lib/steem/operation/set_withdraw_vesting_route.rb +6 -0
- data/lib/steem/operation/transfer.rb +6 -0
- data/lib/steem/operation/transfer_from_savings.rb +7 -0
- data/lib/steem/operation/transfer_to_savings.rb +6 -0
- data/lib/steem/operation/transfer_to_vesting.rb +5 -0
- data/lib/steem/operation/vote.rb +6 -0
- data/lib/steem/operation/withdraw_vesting.rb +4 -0
- data/lib/steem/operation/witness_set_properties.rb +5 -0
- data/lib/steem/operation/witness_update.rb +7 -0
- data/lib/steem/rpc/base_client.rb +13 -0
- data/lib/steem/rpc/http_client.rb +17 -1
- data/lib/steem/stream.rb +12 -4
- data/lib/steem/transaction.rb +96 -0
- data/lib/steem/transaction_builder.rb +69 -69
- data/lib/steem/type/amount.rb +2 -0
- data/lib/steem/version.rb +1 -1
- data/steem-ruby.gemspec +2 -0
- metadata +90 -2
@@ -161,6 +161,19 @@ module Steem
|
|
161
161
|
|
162
162
|
sleep @backoff
|
163
163
|
end
|
164
|
+
|
165
|
+
# @private
|
166
|
+
def raise_error_response(rpc_method_name, rpc_args, response)
|
167
|
+
raise UnknownError, "#{rpc_method_name}: #{response}" if response.error.nil?
|
168
|
+
|
169
|
+
error = response.error
|
170
|
+
|
171
|
+
if error.message == 'Invalid Request'
|
172
|
+
raise Steem::ArgumentError, "Unexpected arguments: #{rpc_args.inspect}. Expected: #{rpc_method_name} (#{args_keys_to_s(rpc_method_name)})"
|
173
|
+
end
|
174
|
+
|
175
|
+
BaseError.build_error(error, rpc_method_name)
|
176
|
+
end
|
164
177
|
end
|
165
178
|
end
|
166
179
|
end
|
@@ -17,7 +17,7 @@ module Steem
|
|
17
17
|
#
|
18
18
|
# @private
|
19
19
|
TIMEOUT_ERRORS = [Net::OpenTimeout, JSON::ParserError, Net::ReadTimeout,
|
20
|
-
Errno::EBADF, IOError, Errno::ENETDOWN]
|
20
|
+
Errno::EBADF, IOError, Errno::ENETDOWN, Steem::RemoteDatabaseLockError]
|
21
21
|
|
22
22
|
# @private
|
23
23
|
POST_HEADERS = {
|
@@ -108,6 +108,22 @@ module Steem
|
|
108
108
|
else; response
|
109
109
|
end
|
110
110
|
|
111
|
+
[response].flatten.each_with_index do |r, i|
|
112
|
+
if defined?(r.error) && !!r.error
|
113
|
+
if !!r.error.message
|
114
|
+
begin
|
115
|
+
rpc_method_name = "#{api_name}.#{api_method}"
|
116
|
+
rpc_args = [request_object].flatten[i]
|
117
|
+
raise_error_response rpc_method_name, rpc_args, r
|
118
|
+
rescue *TIMEOUT_ERRORS => e
|
119
|
+
throw retry_timeout(:tota_cera_pila, e)
|
120
|
+
end
|
121
|
+
else
|
122
|
+
raise Steem::ArgumentError, r.error.inspect
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
111
127
|
yield_response response, &block
|
112
128
|
when '504' # Gateway Timeout
|
113
129
|
throw retry_timeout(:tota_cera_pila, response.body)
|
data/lib/steem/stream.rb
CHANGED
@@ -91,6 +91,14 @@ module Steem
|
|
91
91
|
# @option options [Integer] :until_block_num Ends the stream at the given block number. Default: nil.
|
92
92
|
def transactions(options = {}, &block)
|
93
93
|
blocks(options) do |block, block_num|
|
94
|
+
if block.nil?
|
95
|
+
warn "Batch missing block_num: #{block_num}, retrying ..."
|
96
|
+
|
97
|
+
block = block_api.get_block(block_num: block_num) do |result|
|
98
|
+
result.block
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
94
102
|
block.transactions.each_with_index do |transaction, index|
|
95
103
|
trx_id = block.transaction_ids[index]
|
96
104
|
|
@@ -332,9 +340,8 @@ module Steem
|
|
332
340
|
end
|
333
341
|
|
334
342
|
response = account_history_api.get_ops_in_block(*get_ops_in_block_options)
|
335
|
-
result = response.result
|
336
343
|
|
337
|
-
if result.nil?
|
344
|
+
if response.nil? || (result = response.result).nil?
|
338
345
|
if retries < MAX_RETRY_COUNT
|
339
346
|
warn "Retrying get_ops_in_block on block #{block_num}" unless @no_warn
|
340
347
|
retries = retries + 1
|
@@ -360,7 +367,8 @@ module Steem
|
|
360
367
|
retries = retries + 1
|
361
368
|
redo
|
362
369
|
else
|
363
|
-
|
370
|
+
warn "unable to find virtual operations for block: #{block_num}"
|
371
|
+
# raise TooManyRetriesError, "unable to find virtual operations for block: #{block_num}"
|
364
372
|
end
|
365
373
|
end
|
366
374
|
|
@@ -374,4 +382,4 @@ module Steem
|
|
374
382
|
end
|
375
383
|
end
|
376
384
|
end
|
377
|
-
end
|
385
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Steem
|
2
|
+
class Transaction
|
3
|
+
include JSONable
|
4
|
+
include Utils
|
5
|
+
|
6
|
+
ATTRIBUTES = %i(id ref_block_num ref_block_prefix expiration operations
|
7
|
+
extensions signatures)
|
8
|
+
|
9
|
+
attr_accessor *ATTRIBUTES
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
if !!(hex = options.delete(:hex))
|
13
|
+
marshal = Marshal.new(hex: hex)
|
14
|
+
marshal.transaction(trx: self)
|
15
|
+
end
|
16
|
+
|
17
|
+
options.each do |k, v|
|
18
|
+
raise Steem::ArgumentError, "Invalid option specified: #{k}" unless ATTRIBUTES.include?(k.to_sym)
|
19
|
+
|
20
|
+
send("#{k}=", v)
|
21
|
+
end
|
22
|
+
|
23
|
+
self.operations ||= []
|
24
|
+
self.extensions ||= []
|
25
|
+
self.signatures ||= []
|
26
|
+
|
27
|
+
self.expiration = case @expiration
|
28
|
+
when String then Time.parse(@expiration + 'Z')
|
29
|
+
else; @expiration
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def inspect
|
34
|
+
properties = ATTRIBUTES.map do |prop|
|
35
|
+
unless (v = instance_variable_get("@#{prop}")).nil?
|
36
|
+
v = if v.respond_to? :strftime
|
37
|
+
v.strftime('%Y-%m-%dT%H:%M:%S')
|
38
|
+
else
|
39
|
+
v
|
40
|
+
end
|
41
|
+
|
42
|
+
"@#{prop}=#{v}"
|
43
|
+
end
|
44
|
+
end.compact.join(', ')
|
45
|
+
|
46
|
+
"#<#{self.class.name} [#{properties}]>"
|
47
|
+
end
|
48
|
+
|
49
|
+
def expiration
|
50
|
+
if @expiration.respond_to? :strftime
|
51
|
+
@expiration.strftime('%Y-%m-%dT%H:%M:%S')
|
52
|
+
else
|
53
|
+
@expiration
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def expired?
|
58
|
+
@expiration.nil? || @expiration < Time.now
|
59
|
+
end
|
60
|
+
|
61
|
+
def [](key)
|
62
|
+
key = key.to_sym
|
63
|
+
send(key) if self.class.attributes.include?(key)
|
64
|
+
end
|
65
|
+
|
66
|
+
def []=(key, value)
|
67
|
+
key = key.to_sym
|
68
|
+
send("#{key}=", value) if self.class.attributes.include?(key)
|
69
|
+
end
|
70
|
+
|
71
|
+
def ==(other_trx)
|
72
|
+
return true if self.equal? other_trx
|
73
|
+
return false unless self.class == other_trx.class
|
74
|
+
|
75
|
+
begin
|
76
|
+
return false if self[:ref_block_num].to_i != other_trx[:ref_block_num].to_i
|
77
|
+
return false if self[:ref_block_prefix].to_i != other_trx[:ref_block_prefix].to_i
|
78
|
+
return false if self[:expiration].to_i != other_trx[:expiration].to_i
|
79
|
+
return false if self[:operations].size != other_trx[:operations].size
|
80
|
+
|
81
|
+
op_values = self[:operations].map do |type, value|
|
82
|
+
[type.to_s, value.values.map{|v| v.to_s.gsub(/[^a-zA-Z0-9-]/, '')}]
|
83
|
+
end.flatten.sort
|
84
|
+
|
85
|
+
other_op_values = other_trx[:operations].map do |type, value|
|
86
|
+
[type.to_s, value.values.map{|v| v.to_s.gsub(/[^a-zA-Z0-9-]/, '')}]
|
87
|
+
end.flatten.sort
|
88
|
+
# binding.pry unless op_values == other_op_values
|
89
|
+
op_values == other_op_values
|
90
|
+
rescue => e
|
91
|
+
# binding.pry
|
92
|
+
false
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -28,10 +28,11 @@ module Steem
|
|
28
28
|
|
29
29
|
attr_accessor :app_base, :database_api, :block_api, :expiration, :operations
|
30
30
|
attr_writer :wif
|
31
|
-
attr_reader :signed, :testnet
|
31
|
+
attr_reader :signed, :testnet, :force_serialize
|
32
32
|
|
33
33
|
alias app_base? app_base
|
34
34
|
alias testnet? testnet
|
35
|
+
alias force_serialize? force_serialize
|
35
36
|
|
36
37
|
def initialize(options = {})
|
37
38
|
@app_base = !!options[:app_base] # default false
|
@@ -49,6 +50,7 @@ module Steem
|
|
49
50
|
@wif = [options[:wif]].flatten
|
50
51
|
@signed = false
|
51
52
|
@testnet = !!options[:testnet]
|
53
|
+
@force_serialize = !!options[:force_serialize]
|
52
54
|
|
53
55
|
if !!(trx = options[:trx])
|
54
56
|
trx = case trx
|
@@ -56,28 +58,10 @@ module Steem
|
|
56
58
|
else; trx
|
57
59
|
end
|
58
60
|
|
59
|
-
|
60
|
-
ref_block_num: trx['ref_block_num'],
|
61
|
-
ref_block_prefix: trx['ref_block_prefix'],
|
62
|
-
extensions: (trx['extensions']),
|
63
|
-
operations: trx['operations'],
|
64
|
-
signatures: (trx['signatures']),
|
65
|
-
}
|
66
|
-
|
67
|
-
trx_options[:expiration] = case trx['expiration']
|
68
|
-
when String then Time.parse(trx['expiration'] + 'Z')
|
69
|
-
else; trx['expiration']
|
70
|
-
end
|
71
|
-
|
72
|
-
options = options.merge(trx_options)
|
61
|
+
@trx = Transaction.new(trx)
|
73
62
|
end
|
74
63
|
|
75
|
-
@
|
76
|
-
@ref_block_prefix = options[:ref_block_prefix]
|
77
|
-
@operations = options[:operations] || []
|
78
|
-
@expiration = options[:expiration]
|
79
|
-
@extensions = options[:extensions] || []
|
80
|
-
@signatures = options[:signatures] || []
|
64
|
+
@trx ||= Transaction.new
|
81
65
|
@chain = options[:chain] || :steem
|
82
66
|
@error_pipe = options[:error_pipe] || STDERR
|
83
67
|
@chain_id = options[:chain_id]
|
@@ -93,12 +77,9 @@ module Steem
|
|
93
77
|
end
|
94
78
|
|
95
79
|
def inspect
|
96
|
-
properties = %w(
|
97
|
-
ref_block_num ref_block_prefix expiration operations extensions
|
98
|
-
signatures
|
99
|
-
).map do |prop|
|
80
|
+
properties = %w(trx).map do |prop|
|
100
81
|
if !!(v = instance_variable_get("@#{prop}"))
|
101
|
-
"@#{prop}=#{v}"
|
82
|
+
"@#{prop}=#{v.inspect}"
|
102
83
|
end
|
103
84
|
end.compact.join(', ')
|
104
85
|
|
@@ -106,21 +87,12 @@ module Steem
|
|
106
87
|
end
|
107
88
|
|
108
89
|
def reset
|
109
|
-
@
|
110
|
-
@ref_block_prefix = nil
|
111
|
-
@expiration = nil
|
112
|
-
@operations = []
|
113
|
-
@extensions = []
|
114
|
-
@signatures = []
|
90
|
+
@trx = Transaction.new
|
115
91
|
@signed = false
|
116
92
|
|
117
93
|
self
|
118
94
|
end
|
119
95
|
|
120
|
-
def expired?
|
121
|
-
@expiration.nil? || @expiration < Time.now
|
122
|
-
end
|
123
|
-
|
124
96
|
# If the transaction can be prepared, this method will do so and set the
|
125
97
|
# expiration. Once the expiration is set, it will not re-prepare. If you
|
126
98
|
# call {#put}, the expiration is set {::Nil} so that it can be re-prepared.
|
@@ -129,7 +101,7 @@ module Steem
|
|
129
101
|
#
|
130
102
|
# @return {TransactionBuilder}
|
131
103
|
def prepare
|
132
|
-
if expired?
|
104
|
+
if @trx.expired?
|
133
105
|
catch :prepare_header do; begin
|
134
106
|
@database_api.get_dynamic_global_properties do |properties|
|
135
107
|
block_number = properties.last_irreversible_block_num
|
@@ -146,9 +118,9 @@ module Steem
|
|
146
118
|
result
|
147
119
|
end
|
148
120
|
|
149
|
-
@ref_block_num = (block_number - 1) & 0xFFFF
|
150
|
-
@ref_block_prefix = unhexlify(header.previous[8..-1]).unpack('V*')[0]
|
151
|
-
@expiration ||= (Time.parse(properties.time + 'Z') + EXPIRE_IN_SECS).utc
|
121
|
+
@trx.ref_block_num = (block_number - 1) & 0xFFFF
|
122
|
+
@trx.ref_block_prefix = unhexlify(header.previous[8..-1]).unpack('V*')[0]
|
123
|
+
@trx.expiration ||= (Time.parse(properties.time + 'Z') + EXPIRE_IN_SECS).utc
|
152
124
|
end
|
153
125
|
end
|
154
126
|
rescue => e
|
@@ -166,9 +138,9 @@ module Steem
|
|
166
138
|
|
167
139
|
# Sets operations all at once, then prepares.
|
168
140
|
def operations=(operations)
|
169
|
-
@operations = operations.map{ |op| normalize_operation(op) }
|
141
|
+
@trx.operations = operations.map{ |op| normalize_operation(op) }
|
170
142
|
prepare
|
171
|
-
@operations
|
143
|
+
@trx.operations
|
172
144
|
end
|
173
145
|
|
174
146
|
# A quick and flexible way to append a new operation to the transaction.
|
@@ -194,8 +166,8 @@ module Steem
|
|
194
166
|
# builder.put(vote: vote1).put(vote: vote2)
|
195
167
|
# @return {TransactionBuilder}
|
196
168
|
def put(type, op = nil)
|
197
|
-
@expiration = nil
|
198
|
-
@operations << normalize_operation(type, op)
|
169
|
+
@trx.expiration = nil
|
170
|
+
@trx.operations << normalize_operation(type, op)
|
199
171
|
prepare
|
200
172
|
self
|
201
173
|
end
|
@@ -218,9 +190,17 @@ module Steem
|
|
218
190
|
# ]],
|
219
191
|
# :signatures => ["1c45b65740b4b2c17c4bcf6bcc3f8d90ddab827d50532729fc3b8f163f2c465a532b0112ae4bf388ccc97b7c2e0bc570caadda78af48cf3c261037e65eefcd941e"]
|
220
192
|
# }
|
221
|
-
def transaction
|
222
|
-
prepare
|
223
|
-
sign
|
193
|
+
def transaction(options = {prepare: true, sign: true})
|
194
|
+
options[:prepare] = true unless options.has_key? :prepare
|
195
|
+
options[:sign] = true unless options.has_key? :sign
|
196
|
+
|
197
|
+
prepare if !!options[:prepare]
|
198
|
+
|
199
|
+
if !!options[:sign]
|
200
|
+
sign
|
201
|
+
else
|
202
|
+
@trx
|
203
|
+
end
|
224
204
|
end
|
225
205
|
|
226
206
|
# Appends to the `signatures` array of the transaction, built from a
|
@@ -229,39 +209,42 @@ module Steem
|
|
229
209
|
# @return {Hash | TransactionBuilder} The fully signed transaction if a `wif` is provided or the instance of the {TransactionBuilder} if a `wif` has not yet been provided.
|
230
210
|
def sign
|
231
211
|
return self if @wif.empty?
|
232
|
-
return self if expired?
|
233
|
-
|
234
|
-
trx = {
|
235
|
-
ref_block_num: @ref_block_num,
|
236
|
-
ref_block_prefix: @ref_block_prefix,
|
237
|
-
expiration: @expiration.strftime('%Y-%m-%dT%H:%M:%S'),
|
238
|
-
operations: @operations,
|
239
|
-
extensions: @extensions,
|
240
|
-
signatures: @signatures
|
241
|
-
}
|
212
|
+
return self if @trx.expired?
|
242
213
|
|
243
214
|
unless @signed
|
244
215
|
catch :serialize do; begin
|
245
|
-
|
246
|
-
{trx: trx}
|
247
|
-
else
|
248
|
-
trx
|
249
|
-
end
|
250
|
-
|
251
|
-
@database_api.get_transaction_hex(transaction_hex_args) do |result|
|
216
|
+
transaction_hex.tap do |result|
|
252
217
|
hex = if app_base?
|
253
218
|
result.hex
|
254
219
|
else
|
255
220
|
result
|
256
221
|
end
|
222
|
+
|
223
|
+
unless force_serialize?
|
224
|
+
derrived_trx = Transaction.new(hex: hex)
|
225
|
+
derrived_ops = derrived_trx.operations
|
226
|
+
derrived_trx.operations = derrived_ops.map do |op|
|
227
|
+
op_name = if app_base?
|
228
|
+
op[:type].to_sym
|
229
|
+
else
|
230
|
+
op[:type].to_s.sub(/_operation$/, '').to_sym
|
231
|
+
end
|
232
|
+
|
233
|
+
normalize_operation op_name, JSON[op[:value].to_json]
|
234
|
+
end
|
235
|
+
|
236
|
+
raise SerializationMismatchError unless @trx == derrived_trx
|
237
|
+
end
|
238
|
+
|
239
|
+
hex = hex[0..-4] # drop empty signature array
|
240
|
+
@trx.id = Digest::SHA256.hexdigest(unhexlify(hex))[0..39]
|
257
241
|
|
258
|
-
hex = @chain_id + hex
|
242
|
+
hex = @chain_id + hex
|
259
243
|
digest = unhexlify(hex)
|
260
244
|
digest_hex = Digest::SHA256.digest(digest)
|
261
245
|
private_keys = @wif.map{ |wif| Bitcoin::Key.from_base58 wif }
|
262
246
|
ec = Bitcoin::OpenSSL_EC
|
263
247
|
count = 0
|
264
|
-
sigs = []
|
265
248
|
|
266
249
|
private_keys.each do |private_key|
|
267
250
|
sig = nil
|
@@ -276,11 +259,10 @@ module Steem
|
|
276
259
|
break if canonical? sig
|
277
260
|
end
|
278
261
|
|
279
|
-
@signatures << hexlify(sig)
|
262
|
+
@trx.signatures << hexlify(sig)
|
280
263
|
end
|
281
264
|
|
282
265
|
@signed = true
|
283
|
-
trx[:signatures] = @signatures
|
284
266
|
end
|
285
267
|
rescue => e
|
286
268
|
if can_retry? e
|
@@ -292,7 +274,25 @@ module Steem
|
|
292
274
|
end; end
|
293
275
|
end
|
294
276
|
|
295
|
-
|
277
|
+
@trx
|
278
|
+
end
|
279
|
+
|
280
|
+
def transaction_hex
|
281
|
+
trx = transaction(prepare: true, sign: false)
|
282
|
+
|
283
|
+
transaction_hex_args = if app_base?
|
284
|
+
{trx: trx}
|
285
|
+
else
|
286
|
+
trx
|
287
|
+
end
|
288
|
+
|
289
|
+
@database_api.get_transaction_hex(transaction_hex_args) do |result|
|
290
|
+
if app_base?
|
291
|
+
result[:hex]
|
292
|
+
else
|
293
|
+
result
|
294
|
+
end
|
295
|
+
end
|
296
296
|
end
|
297
297
|
|
298
298
|
# @return [Array] All public keys that could possibly sign for a given transaction.
|