steem-ruby 0.9.0 → 0.9.5

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