steem-ruby 0.9.0 → 0.9.5

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.
Files changed (68) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -1
  3. data/README.md +88 -15
  4. data/Rakefile +128 -31
  5. data/lib/steem.rb +49 -0
  6. data/lib/steem/api.rb +39 -37
  7. data/lib/steem/base_error.rb +80 -41
  8. data/lib/steem/block_api.rb +23 -3
  9. data/lib/steem/broadcast.rb +465 -29
  10. data/lib/steem/chain_config.rb +3 -3
  11. data/lib/steem/marshal.rb +231 -0
  12. data/lib/steem/mixins/jsonable.rb +37 -0
  13. data/lib/steem/mixins/retriable.rb +30 -24
  14. data/lib/steem/mixins/serializable.rb +45 -0
  15. data/lib/steem/operation.rb +141 -0
  16. data/lib/steem/operation/account_create.rb +10 -0
  17. data/lib/steem/operation/account_create_with_delegation.rb +12 -0
  18. data/lib/steem/operation/account_update.rb +8 -0
  19. data/lib/steem/operation/account_witness_proxy.rb +4 -0
  20. data/lib/steem/operation/account_witness_vote.rb +5 -0
  21. data/lib/steem/operation/cancel_transfer_from_savings.rb +4 -0
  22. data/lib/steem/operation/challenge_authority.rb +5 -0
  23. data/lib/steem/operation/change_recovery_account.rb +5 -0
  24. data/lib/steem/operation/claim_account.rb +5 -0
  25. data/lib/steem/operation/claim_reward_balance.rb +6 -0
  26. data/lib/steem/operation/comment.rb +9 -0
  27. data/lib/steem/operation/comment_options.rb +10 -0
  28. data/lib/steem/operation/convert.rb +5 -0
  29. data/lib/steem/operation/create_claimed_account.rb +10 -0
  30. data/lib/steem/operation/custom.rb +5 -0
  31. data/lib/steem/operation/custom_binary.rb +8 -0
  32. data/lib/steem/operation/custom_json.rb +6 -0
  33. data/lib/steem/operation/decline_voting_rights.rb +4 -0
  34. data/lib/steem/operation/delegate_vesting_shares.rb +5 -0
  35. data/lib/steem/operation/delete_comment.rb +4 -0
  36. data/lib/steem/operation/escrow_approve.rb +8 -0
  37. data/lib/steem/operation/escrow_dispute.rb +7 -0
  38. data/lib/steem/operation/escrow_release.rb +10 -0
  39. data/lib/steem/operation/escrow_transfer.rb +12 -0
  40. data/lib/steem/operation/feed_publish.rb +4 -0
  41. data/lib/steem/operation/limit_order_cancel.rb +4 -0
  42. data/lib/steem/operation/limit_order_create.rb +8 -0
  43. data/lib/steem/operation/limit_order_create2.rb +8 -0
  44. data/lib/steem/operation/prove_authority.rb +4 -0
  45. data/lib/steem/operation/recover_account.rb +6 -0
  46. data/lib/steem/operation/report_over_production.rb +5 -0
  47. data/lib/steem/operation/request_account_recovery.rb +6 -0
  48. data/lib/steem/operation/reset_account.rb +5 -0
  49. data/lib/steem/operation/set_reset_account.rb +5 -0
  50. data/lib/steem/operation/set_withdraw_vesting_route.rb +6 -0
  51. data/lib/steem/operation/transfer.rb +6 -0
  52. data/lib/steem/operation/transfer_from_savings.rb +7 -0
  53. data/lib/steem/operation/transfer_to_savings.rb +6 -0
  54. data/lib/steem/operation/transfer_to_vesting.rb +5 -0
  55. data/lib/steem/operation/vote.rb +6 -0
  56. data/lib/steem/operation/withdraw_vesting.rb +4 -0
  57. data/lib/steem/operation/witness_set_properties.rb +5 -0
  58. data/lib/steem/operation/witness_update.rb +7 -0
  59. data/lib/steem/rpc/base_client.rb +16 -4
  60. data/lib/steem/rpc/http_client.rb +18 -2
  61. data/lib/steem/stream.rb +385 -0
  62. data/lib/steem/transaction.rb +96 -0
  63. data/lib/steem/transaction_builder.rb +176 -103
  64. data/lib/steem/type/amount.rb +61 -9
  65. data/lib/steem/version.rb +1 -1
  66. data/steem-ruby.gemspec +9 -4
  67. metadata +203 -56
  68. data/Gemfile.lock +0 -73
@@ -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
@@ -1,3 +1,5 @@
1
+ require 'pry'
2
+
1
3
  module Steem
2
4
  # {TransactionBuilder} can be used to create a transaction that the
3
5
  # {NetworkBroadcastApi} can broadcast to the rest of the platform. The main
@@ -15,7 +17,7 @@ module Steem
15
17
  # })
16
18
  #
17
19
  # trx = builder.transaction
18
- # network_broadcast_api = Steem::NetworkBroadcastApi.new
20
+ # network_broadcast_api = Steem::CondenserApi.new
19
21
  # network_broadcast_api.broadcast_transaction_synchronous(trx: trx)
20
22
  #
21
23
  #
@@ -26,15 +28,31 @@ module Steem
26
28
  include ChainConfig
27
29
  include Utils
28
30
 
29
- attr_accessor :database_api, :block_api, :expiration, :operations
31
+ attr_accessor :app_base, :database_api, :block_api, :expiration, :operations
30
32
  attr_writer :wif
31
- attr_reader :signed
33
+ attr_reader :signed, :testnet, :force_serialize
34
+
35
+ alias app_base? app_base
36
+ alias testnet? testnet
37
+ alias force_serialize? force_serialize
32
38
 
33
39
  def initialize(options = {})
34
- @database_api = options[:database_api] || Steem::DatabaseApi.new(options)
35
- @block_api = options[:block_api] || Steem::BlockApi.new(options)
40
+ @app_base = true#!!options[:app_base] # default false
41
+ @database_api = options[:database_api]
42
+ @block_api = options[:block_api]
43
+
44
+ if app_base?
45
+ @database_api ||= Steem::DatabaseApi.new(options)
46
+ @block_api ||= Steem::BlockApi.new(options)
47
+ else
48
+ @database_api ||= Steem::CondenserApi.new(options)
49
+ @block_api ||= Steem::CondenserApi.new(options)
50
+ end
51
+
36
52
  @wif = [options[:wif]].flatten
37
53
  @signed = false
54
+ @testnet = true#!!options[:testnet]
55
+ @force_serialize = true#!!options[:force_serialize]
38
56
 
39
57
  if !!(trx = options[:trx])
40
58
  trx = case trx
@@ -42,44 +60,28 @@ module Steem
42
60
  else; trx
43
61
  end
44
62
 
45
- trx_options = {
46
- ref_block_num: trx['ref_block_num'],
47
- ref_block_prefix: trx['ref_block_prefix'],
48
- extensions: (trx['extensions']),
49
- operations: trx['operations'],
50
- signatures: (trx['signatures']),
51
- }
52
-
53
- trx_options[:expiration] = case trx['expiration']
54
- when String then Time.parse(trx['expiration'] + 'Z')
55
- else; trx['expiration']
56
- end
57
-
58
- options = options.merge(trx_options)
63
+ @trx = Transaction.new(trx)
59
64
  end
60
65
 
61
- @ref_block_num = options[:ref_block_num]
62
- @ref_block_prefix = options[:ref_block_prefix]
63
- @operations = options[:operations] || []
64
- @expiration = options[:expiration]
65
- @extensions = options[:extensions] || []
66
- @signatures = options[:signatures] || []
67
- @chain = options[:chain] || :steem
66
+ @trx ||= Transaction.new
67
+ @chain = options[:chain] || :test
68
68
  @error_pipe = options[:error_pipe] || STDERR
69
- @chain_id = case @chain
69
+ @chain_id = options[:chain_id]
70
+ @chain_id ||= case @chain
70
71
  when :steem then NETWORKS_STEEM_CHAIN_ID
71
72
  when :test then NETWORKS_TEST_CHAIN_ID
72
73
  else; raise UnsupportedChainError, "Unsupported chain: #{@chain}"
73
74
  end
75
+
76
+ # if testnet? && @chain_id == NETWORKS_STEEM_CHAIN_ID
77
+ # raise UnsupportedChainError, "Unsupported testnet chain id: #{@chain_id}"
78
+ # end
74
79
  end
75
80
 
76
81
  def inspect
77
- properties = %w(
78
- ref_block_num ref_block_prefix expiration operations extensions
79
- signatures
80
- ).map do |prop|
82
+ properties = %w(trx).map do |prop|
81
83
  if !!(v = instance_variable_get("@#{prop}"))
82
- "@#{prop}=#{v}"
84
+ "@#{prop}=#{v.inspect}"
83
85
  end
84
86
  end.compact.join(', ')
85
87
 
@@ -87,21 +89,12 @@ module Steem
87
89
  end
88
90
 
89
91
  def reset
90
- @ref_block_num = nil
91
- @ref_block_prefix = nil
92
- @expiration = nil
93
- @operations = []
94
- @extensions = []
95
- @signatures = []
92
+ @trx = Transaction.new
96
93
  @signed = false
97
94
 
98
95
  self
99
96
  end
100
97
 
101
- def expired?
102
- @expiration.nil? || @expiration < Time.now
103
- end
104
-
105
98
  # If the transaction can be prepared, this method will do so and set the
106
99
  # expiration. Once the expiration is set, it will not re-prepare. If you
107
100
  # call {#put}, the expiration is set {::Nil} so that it can be re-prepared.
@@ -110,17 +103,26 @@ module Steem
110
103
  #
111
104
  # @return {TransactionBuilder}
112
105
  def prepare
113
- if expired?
106
+ if @trx.expired?
114
107
  catch :prepare_header do; begin
115
108
  @database_api.get_dynamic_global_properties do |properties|
116
109
  block_number = properties.last_irreversible_block_num
110
+ block_header_args = if app_base?
111
+ {block_num: block_number}
112
+ else
113
+ block_number
114
+ end
117
115
 
118
- @block_api.get_block_header(block_num: block_number) do |result|
119
- header = result.header
116
+ @block_api.get_block_header(block_header_args) do |result|
117
+ header = if app_base?
118
+ result.header
119
+ else
120
+ result
121
+ end
120
122
 
121
- @ref_block_num = (block_number - 1) & 0xFFFF
122
- @ref_block_prefix = unhexlify(header.previous[8..-1]).unpack('V*')[0]
123
- @expiration ||= (Time.parse(properties.time + 'Z') + EXPIRE_IN_SECS).utc
123
+ @trx.ref_block_num = (block_number - 1) & 0xFFFF
124
+ @trx.ref_block_prefix = unhexlify(header.previous[8..-1]).unpack('V*')[0]
125
+ @trx.expiration ||= (Time.parse(properties.time + 'Z') + EXPIRE_IN_SECS).utc
124
126
  end
125
127
  end
126
128
  rescue => e
@@ -138,9 +140,9 @@ module Steem
138
140
 
139
141
  # Sets operations all at once, then prepares.
140
142
  def operations=(operations)
141
- @operations = operations
143
+ @trx.operations = operations.map{ |op| normalize_operation(op) }
142
144
  prepare
143
- @operations
145
+ @trx.operations
144
146
  end
145
147
 
146
148
  # A quick and flexible way to append a new operation to the transaction.
@@ -166,35 +168,9 @@ module Steem
166
168
  # builder.put(vote: vote1).put(vote: vote2)
167
169
  # @return {TransactionBuilder}
168
170
  def put(type, op = nil)
169
- @expiration = nil
170
-
171
- ## Saving this for later. This block, or something like it, might replace
172
- ## API broadcast operation structure.
173
- # case type
174
- # when Symbol, String
175
- # type_value = "#{type}_operation"
176
- # @operations << {type: type_value, value: op}
177
- # when Hash
178
- # type_value = "#{type.keys.first}_operation"
179
- # @operations << {type: type_value, value: type.values.first}
180
- # when Array
181
- # type_value = "#{type[0]}_operation"
182
- # @operations << {type: type_value, value: type[1]}
183
- # else
184
- # # don't know what to do with it, skipped
185
- # end
186
-
187
- case type
188
- when Symbol then @operations << [type, op]
189
- when String then @operations << [type.to_sym, op]
190
- when Hash then @operations << [type.keys.first.to_sym, type.values.first]
191
- when Array then @operations << type
192
- else
193
- # don't know what to do with it, skipped
194
- end
195
-
171
+ @trx.expiration = nil
172
+ @trx.operations << normalize_operation(type, op)
196
173
  prepare
197
-
198
174
  self
199
175
  end
200
176
 
@@ -216,9 +192,17 @@ module Steem
216
192
  # ]],
217
193
  # :signatures => ["1c45b65740b4b2c17c4bcf6bcc3f8d90ddab827d50532729fc3b8f163f2c465a532b0112ae4bf388ccc97b7c2e0bc570caadda78af48cf3c261037e65eefcd941e"]
218
194
  # }
219
- def transaction
220
- prepare
221
- sign
195
+ def transaction(options = {prepare: true, sign: true})
196
+ options[:prepare] = true unless options.has_key? :prepare
197
+ options[:sign] = true unless options.has_key? :sign
198
+
199
+ prepare if !!options[:prepare]
200
+
201
+ if !!options[:sign]
202
+ sign
203
+ else
204
+ @trx
205
+ end
222
206
  end
223
207
 
224
208
  # Appends to the `signatures` array of the transaction, built from a
@@ -227,27 +211,42 @@ module Steem
227
211
  # @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.
228
212
  def sign
229
213
  return self if @wif.empty?
230
- return self if expired?
231
-
232
- trx = {
233
- ref_block_num: @ref_block_num,
234
- ref_block_prefix: @ref_block_prefix,
235
- expiration: @expiration.strftime('%Y-%m-%dT%H:%M:%S'),
236
- operations: @operations,
237
- extensions: @extensions,
238
- signatures: @signatures
239
- }
214
+ return self if @trx.expired?
240
215
 
241
216
  unless @signed
242
217
  catch :serialize do; begin
243
- @database_api.get_transaction_hex(trx: trx) do |result|
244
- hex = @chain_id + result.hex[0..-4] # Why do we have to chop the last two bytes?
218
+ transaction_hex.tap do |result|
219
+ hex = if app_base?
220
+ result
221
+ else
222
+ result
223
+ end
224
+
225
+ unless force_serialize?
226
+ derrived_trx = Transaction.new(hex: hex)
227
+ derrived_ops = derrived_trx.operations
228
+ derrived_trx.operations = derrived_ops.map do |op|
229
+ op_name = if app_base?
230
+ op[:type].to_sym
231
+ else
232
+ op[:type].to_s.sub(/_operation$/, '').to_sym
233
+ end
234
+
235
+ normalize_operation op_name, JSON[op[:value].to_json]
236
+ end
237
+
238
+ raise SerializationMismatchError unless @trx == derrived_trx
239
+ end
240
+
241
+ hex = hex.to_s[0..-4] # drop empty signature array
242
+ @trx.id = Digest::SHA256.hexdigest(unhexlify(hex))[0..39]
243
+
244
+ hex = @chain_id + hex
245
245
  digest = unhexlify(hex)
246
246
  digest_hex = Digest::SHA256.digest(digest)
247
247
  private_keys = @wif.map{ |wif| Bitcoin::Key.from_base58 wif }
248
248
  ec = Bitcoin::OpenSSL_EC
249
249
  count = 0
250
- sigs = []
251
250
 
252
251
  private_keys.each do |private_key|
253
252
  sig = nil
@@ -262,11 +261,10 @@ module Steem
262
261
  break if canonical? sig
263
262
  end
264
263
 
265
- @signatures << hexlify(sig)
264
+ @trx.signatures << hexlify(sig)
266
265
  end
267
266
 
268
267
  @signed = true
269
- trx[:signatures] = @signatures
270
268
  end
271
269
  rescue => e
272
270
  if can_retry? e
@@ -278,13 +276,41 @@ module Steem
278
276
  end; end
279
277
  end
280
278
 
281
- Hashie::Mash.new trx
279
+ @trx
280
+ end
281
+
282
+ def transaction_hex
283
+ trx = transaction(prepare: true, sign: false)
284
+
285
+ transaction_hex_args = if app_base?
286
+ {trx: trx}
287
+ else
288
+ trx
289
+ end
290
+
291
+ @database_api.get_transaction_hex(transaction_hex_args) do |result|
292
+ if app_base?
293
+ result[:hex]
294
+ else
295
+ result
296
+ end
297
+ end
282
298
  end
283
299
 
284
300
  # @return [Array] All public keys that could possibly sign for a given transaction.
285
301
  def potential_signatures
286
- @database_api.get_potential_signatures(trx: transaction) do |result|
287
- result[:keys]
302
+ potential_signatures_args = if app_base?
303
+ {trx: transaction}
304
+ else
305
+ transaction
306
+ end
307
+
308
+ @database_api.get_potential_signatures(potential_signatures_args) do |result|
309
+ if app_base?
310
+ result[:keys]
311
+ else
312
+ result
313
+ end
288
314
  end
289
315
  end
290
316
 
@@ -294,15 +320,35 @@ module Steem
294
320
  #
295
321
  # @return [Array] The minimal subset of public keys that should add signatures to the transaction.
296
322
  def required_signatures
297
- @database_api.get_required_signatures(trx: transaction) do |result|
298
- result[:keys]
323
+ required_signatures_args = if app_base?
324
+ {trx: transaction}
325
+ else
326
+ [transaction, []]
327
+ end
328
+
329
+ @database_api.get_required_signatures(*required_signatures_args) do |result|
330
+ if app_base?
331
+ result[:keys]
332
+ else
333
+ result
334
+ end
299
335
  end
300
336
  end
301
337
 
302
338
  # @return [Boolean] True if the transaction has all of the required signatures.
303
339
  def valid?
304
- @database_api.verify_authority(trx: transaction) do |result|
305
- result.valid
340
+ verify_authority_args = if app_base?
341
+ {trx: transaction}
342
+ else
343
+ transaction
344
+ end
345
+
346
+ @database_api.verify_authority(verify_authority_args) do |result|
347
+ if app_base?
348
+ result.valid
349
+ else
350
+ result
351
+ end
306
352
  end
307
353
  end
308
354
  private
@@ -318,5 +364,32 @@ module Steem
318
364
  ((sig[33] & 0x80 ) != 0)
319
365
  )
320
366
  end
367
+
368
+ def normalize_operation(type, op = nil)
369
+ if app_base?
370
+ case type
371
+ when Symbol, String
372
+ type_value = "#{type}_operation"
373
+ {type: type_value, value: op}
374
+ when Hash
375
+ type_value = "#{type.keys.first}_operation"
376
+ {type: type_value, value: type.values.first}
377
+ when Array
378
+ type_value = "#{type[0]}_operation"
379
+ {type: type_value, value: type[1]}
380
+ else
381
+ raise Steem::ArgumentError, "Don't know what to do with operation type #{type.class}: #{type} (#{op})"
382
+ end
383
+ else
384
+ case type
385
+ when Symbol then [type, op]
386
+ when String then [type.to_sym, op]
387
+ when Hash then [type.keys.first.to_sym, type.values.first]
388
+ when Array then type
389
+ else
390
+ raise Steem::ArgumentError, "Don't know what to do with operation type #{type.class}: #{type} (#{op})"
391
+ end
392
+ end
393
+ end
321
394
  end
322
395
  end