crea-ruby 0.0.1

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 (76) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +55 -0
  3. data/CONTRIBUTING.md +79 -0
  4. data/Gemfile +3 -0
  5. data/LICENSE +22 -0
  6. data/README.md +234 -0
  7. data/Rakefile +332 -0
  8. data/crea-ruby.gemspec +39 -0
  9. data/gource.sh +6 -0
  10. data/lib/crea.rb +85 -0
  11. data/lib/crea/api.rb +208 -0
  12. data/lib/crea/base_error.rb +218 -0
  13. data/lib/crea/block_api.rb +78 -0
  14. data/lib/crea/broadcast.rb +1334 -0
  15. data/lib/crea/chain_config.rb +36 -0
  16. data/lib/crea/formatter.rb +14 -0
  17. data/lib/crea/jsonrpc.rb +108 -0
  18. data/lib/crea/marshal.rb +231 -0
  19. data/lib/crea/mixins/jsonable.rb +37 -0
  20. data/lib/crea/mixins/retriable.rb +58 -0
  21. data/lib/crea/mixins/serializable.rb +45 -0
  22. data/lib/crea/operation.rb +141 -0
  23. data/lib/crea/operation/account_create.rb +10 -0
  24. data/lib/crea/operation/account_create_with_delegation.rb +12 -0
  25. data/lib/crea/operation/account_update.rb +8 -0
  26. data/lib/crea/operation/account_witness_proxy.rb +4 -0
  27. data/lib/crea/operation/account_witness_vote.rb +5 -0
  28. data/lib/crea/operation/cancel_transfer_from_savings.rb +4 -0
  29. data/lib/crea/operation/challenge_authority.rb +5 -0
  30. data/lib/crea/operation/change_recovery_account.rb +5 -0
  31. data/lib/crea/operation/claim_account.rb +5 -0
  32. data/lib/crea/operation/claim_reward_balance.rb +6 -0
  33. data/lib/crea/operation/comment.rb +9 -0
  34. data/lib/crea/operation/comment_options.rb +10 -0
  35. data/lib/crea/operation/convert.rb +5 -0
  36. data/lib/crea/operation/create_claimed_account.rb +10 -0
  37. data/lib/crea/operation/custom.rb +5 -0
  38. data/lib/crea/operation/custom_binary.rb +8 -0
  39. data/lib/crea/operation/custom_json.rb +6 -0
  40. data/lib/crea/operation/decline_voting_rights.rb +4 -0
  41. data/lib/crea/operation/delegate_vesting_shares.rb +5 -0
  42. data/lib/crea/operation/delete_comment.rb +4 -0
  43. data/lib/crea/operation/escrow_approve.rb +8 -0
  44. data/lib/crea/operation/escrow_dispute.rb +7 -0
  45. data/lib/crea/operation/escrow_release.rb +10 -0
  46. data/lib/crea/operation/escrow_transfer.rb +12 -0
  47. data/lib/crea/operation/feed_publish.rb +4 -0
  48. data/lib/crea/operation/limit_order_cancel.rb +4 -0
  49. data/lib/crea/operation/limit_order_create.rb +8 -0
  50. data/lib/crea/operation/limit_order_create2.rb +8 -0
  51. data/lib/crea/operation/prove_authority.rb +4 -0
  52. data/lib/crea/operation/recover_account.rb +6 -0
  53. data/lib/crea/operation/report_over_production.rb +5 -0
  54. data/lib/crea/operation/request_account_recovery.rb +6 -0
  55. data/lib/crea/operation/reset_account.rb +5 -0
  56. data/lib/crea/operation/set_reset_account.rb +5 -0
  57. data/lib/crea/operation/set_withdraw_vesting_route.rb +6 -0
  58. data/lib/crea/operation/transfer.rb +6 -0
  59. data/lib/crea/operation/transfer_from_savings.rb +7 -0
  60. data/lib/crea/operation/transfer_to_savings.rb +6 -0
  61. data/lib/crea/operation/transfer_to_vesting.rb +5 -0
  62. data/lib/crea/operation/vote.rb +6 -0
  63. data/lib/crea/operation/withdraw_vesting.rb +4 -0
  64. data/lib/crea/operation/witness_set_properties.rb +5 -0
  65. data/lib/crea/operation/witness_update.rb +7 -0
  66. data/lib/crea/rpc/base_client.rb +179 -0
  67. data/lib/crea/rpc/http_client.rb +143 -0
  68. data/lib/crea/rpc/thread_safe_http_client.rb +35 -0
  69. data/lib/crea/stream.rb +385 -0
  70. data/lib/crea/transaction.rb +96 -0
  71. data/lib/crea/transaction_builder.rb +393 -0
  72. data/lib/crea/type/amount.rb +107 -0
  73. data/lib/crea/type/base_type.rb +10 -0
  74. data/lib/crea/utils.rb +17 -0
  75. data/lib/crea/version.rb +4 -0
  76. metadata +478 -0
@@ -0,0 +1,385 @@
1
+ module Crea
2
+ # Crea::Stream allows a live view of the CREA blockchain.
3
+ #
4
+ # Example streaming blocks:
5
+ #
6
+ # stream = Crea::Stream.new
7
+ #
8
+ # stream.blocks do |block, block_num|
9
+ # puts "#{block_num} :: #{block.witness}"
10
+ # end
11
+ #
12
+ # Example streaming transactions:
13
+ #
14
+ # stream = Crea::Stream.new
15
+ #
16
+ # stream.transactions do |trx, trx_id, block_num|
17
+ # puts "#{block_num} :: #{trx_id} :: operations: #{trx.operations.size}"
18
+ # end
19
+ #
20
+ # Example streaming operations:
21
+ #
22
+ # stream = Crea::Stream.new
23
+ #
24
+ # stream.operations do |op, trx_id, block_num|
25
+ # puts "#{block_num} :: #{trx_id} :: #{op.type}: #{op.value.to_json}"
26
+ # end
27
+ #
28
+ # Allows streaming of block headers, full blocks, transactions, operations and
29
+ # virtual operations.
30
+ class Stream
31
+ attr_reader :database_api, :block_api, :account_history_api, :mode
32
+
33
+ BLOCK_INTERVAL = 3
34
+ MAX_BACKOFF_BLOCK_INTERVAL = 30
35
+ MAX_RETRY_COUNT = 10
36
+
37
+ VOP_TRX_ID = ('0' * 40).freeze
38
+
39
+ # @param options [Hash] additional options
40
+ # @option options [Crea::DatabaseApi] :database_api
41
+ # @option options [Crea::BlockApi] :block_api
42
+ # @option options [Crea::AccountHistoryApi || Crea::CondenserApi] :account_history_api
43
+ # @option options [Symbol] :mode we have the choice between
44
+ # * :head the last block
45
+ # * :irreversible the block that is confirmed by 2/3 of all block producers and is thus irreversible!
46
+ # @option options [Boolean] :no_warn do not generate warnings
47
+ def initialize(options = {mode: :irreversible})
48
+ @instance_options = options
49
+ @database_api = options[:database_api] || Crea::DatabaseApi.new(options)
50
+ @block_api = options[:block_api] || Crea::BlockApi.new(options)
51
+ @account_history_api = options[:account_history_api]
52
+ @mode = options[:mode] || :irreversible
53
+ @no_warn = !!options[:no_warn]
54
+ end
55
+
56
+ # Use this method to stream block numbers. This is significantly faster
57
+ # than requesting full blocks and even block headers. Basically, the only
58
+ # thing this method does is call {Crea::Database#get_dynamic_global_properties} at 3 second
59
+ # intervals.
60
+ #
61
+ # @param options [Hash] additional options
62
+ # @option options [Integer] :at_block_num Starts the stream at the given block number. Default: nil.
63
+ # @option options [Integer] :until_block_num Ends the stream at the given block number. Default: nil.
64
+ def block_numbers(options = {}, &block)
65
+ block_objects(options.merge(object: :block_numbers), block)
66
+ end
67
+
68
+ # Use this method to stream block headers. This is quite a bit faster than
69
+ # requesting full blocks.
70
+ #
71
+ # @param options [Hash] additional options
72
+ # @option options [Integer] :at_block_num Starts the stream at the given block number. Default: nil.
73
+ # @option options [Integer] :until_block_num Ends the stream at the given block number. Default: nil.
74
+ def block_headers(options = {}, &block)
75
+ block_objects(options.merge(object: :block_headers), block)
76
+ end
77
+
78
+ # Use this method to stream full blocks.
79
+ #
80
+ # @param options [Hash] additional options
81
+ # @option options [Integer] :at_block_num Starts the stream at the given block number. Default: nil.
82
+ # @option options [Integer] :until_block_num Ends the stream at the given block number. Default: nil.
83
+ def blocks(options = {}, &block)
84
+ block_objects(options.merge(object: :blocks), block)
85
+ end
86
+
87
+ # Use this method to stream each transaction.
88
+ #
89
+ # @param options [Hash] additional options
90
+ # @option options [Integer] :at_block_num Starts the stream at the given block number. Default: nil.
91
+ # @option options [Integer] :until_block_num Ends the stream at the given block number. Default: nil.
92
+ def transactions(options = {}, &block)
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
+
102
+ block.transactions.each_with_index do |transaction, index|
103
+ trx_id = block.transaction_ids[index]
104
+
105
+ yield transaction, trx_id, block_num
106
+ end
107
+ end
108
+ end
109
+
110
+ # Returns the latest operations from the blockchain.
111
+ #
112
+ # stream = Crea::Stream.new
113
+ # stream.operations do |op|
114
+ # puts op.to_json
115
+ # end
116
+ #
117
+ # If symbol are passed to `types` option, then only that operation is
118
+ # returned. Expected symbols are:
119
+ #
120
+ # account_create_operation
121
+ # account_create_with_delegation_operation
122
+ # account_update_operation
123
+ # account_witness_proxy_operation
124
+ # account_witness_vote_operation
125
+ # cancel_transfer_from_savings_operation
126
+ # change_recovery_account_operation
127
+ # claim_reward_balance_operation
128
+ # comment_operation
129
+ # comment_options_operation
130
+ # convert_operation
131
+ # custom_operation
132
+ # custom_json_operation
133
+ # decline_voting_rights_operation
134
+ # delegate_vesting_shares_operation
135
+ # delete_comment_operation
136
+ # escrow_approve_operation
137
+ # escrow_dispute_operation
138
+ # escrow_release_operation
139
+ # escrow_transfer_operation
140
+ # feed_publish_operation
141
+ # limit_order_cancel_operation
142
+ # limit_order_create_operation
143
+ # limit_order_create2_operation
144
+ # pow_operation
145
+ # pow2_operation
146
+ # recover_account_operation
147
+ # request_account_recovery_operation
148
+ # set_withdraw_vesting_route_operation
149
+ # transfer_operation
150
+ # transfer_from_savings_operation
151
+ # transfer_to_savings_operation
152
+ # transfer_to_vesting_operation
153
+ # vote_operation
154
+ # withdraw_vesting_operation
155
+ # witness_update_operation
156
+ #
157
+ # For example, to stream only votes:
158
+ #
159
+ # stream = Crea::Stream.new
160
+ # stream.operations(types: :vote_operation) do |vote|
161
+ # puts vote.to_json
162
+ # end
163
+ #
164
+ # ... Or ...
165
+ #
166
+ # stream = Crea::Stream.new
167
+ # stream.operations(:vote_operation) do |vote|
168
+ # puts vote.to_json
169
+ # end
170
+ #
171
+ # You can also stream virtual operations:
172
+ #
173
+ # stream = Crea::Stream.new
174
+ # stream.operations(types: :author_reward_operation, only_virtual: true) do |vop|
175
+ # v = vop.value
176
+ # puts "#{v.author} got paid for #{v.permlink}: #{[v.cbd_payout, v.crea_payout, v.vesting_payout]}"
177
+ # end
178
+ #
179
+ # ... or multiple virtual operation types;
180
+ #
181
+ # stream = Crea::Stream.new
182
+ # stream.operations(types: [:producer_reward_operation, :author_reward_operation], only_virtual: true) do |vop|
183
+ # puts vop.to_json
184
+ # end
185
+ #
186
+ # ... or all types, including virtual operation types from the head block number:
187
+ #
188
+ # stream = Crea::Stream.new(mode: :head)
189
+ # stream.operations(include_virtual: true) do |op|
190
+ # puts op.to_json
191
+ # end
192
+ #
193
+ # Expected virtual operation types:
194
+ #
195
+ # producer_reward_operation
196
+ # author_reward_operation
197
+ # curation_reward_operation
198
+ # fill_convert_request_operation
199
+ # fill_order_operation
200
+ # fill_vesting_withdraw_operation
201
+ # interest_operation
202
+ # shutdown_witness_operation
203
+ #
204
+ # @param args [Symbol || Array<Symbol> || Hash] the type(s) of operation or hash of expanded options, optional.
205
+ # @option args [Integer] :at_block_num Starts the stream at the given block number. Default: nil.
206
+ # @option args [Integer] :until_block_num Ends the stream at the given block number. Default: nil.
207
+ # @option args [Symbol || Array<Symbol>] :types the type(s) of operation, optional.
208
+ # @option args [Boolean] :only_virtual Only stream virtual options. Setting this true will improve performance because the stream only needs block numbers to then retrieve virtual operations. Default: false.
209
+ # @option args [Boolean] :include_virtual Also stream virtual options. Setting this true will impact performance. Default: false.
210
+ # @param block the block to execute for each result. Yields: |op, trx_id, block_num|
211
+ def operations(*args, &block)
212
+ options = {}
213
+ types = []
214
+ only_virtual = false
215
+ include_virtual = false
216
+ last_block_num = nil
217
+
218
+ case args.first
219
+ when Hash
220
+ options = args.first
221
+ types = transform_types(options[:types])
222
+ only_virtual = !!options[:only_virtual] || false
223
+ include_virtual = !!options[:include_virtual] || only_virtual || false
224
+ when Symbol, Array then types = transform_types(args)
225
+ end
226
+
227
+ if only_virtual
228
+ block_numbers(options) do |block_num|
229
+ get_virtual_ops(types, block_num, block)
230
+ end
231
+ else
232
+ transactions(options) do |transaction, trx_id, block_num|
233
+ transaction.operations.each do |op|
234
+ yield op, trx_id, block_num if types.none? || types.include?(op.type)
235
+
236
+ next unless last_block_num != block_num
237
+
238
+ last_block_num = block_num
239
+
240
+ get_virtual_ops(types, block_num, block) if include_virtual
241
+ end
242
+ end
243
+ end
244
+ end
245
+
246
+ def account_history_api
247
+ @account_history_api ||= begin
248
+ Crea::AccountHistoryApi.new(@instance_options)
249
+ rescue Crea::UnknownApiError => e
250
+ warn "#{e.inspect}, falling back to Crea::CondenserApi." unless @no_warn
251
+ Crea::CondenserApi.new(@instance_options)
252
+ end
253
+ end
254
+ private
255
+ # @private
256
+ def block_objects(options = {}, block)
257
+ object = options[:object]
258
+ object_method = "get_#{object}".to_sym
259
+ block_interval = BLOCK_INTERVAL
260
+
261
+ at_block_num, until_block_num = if !!block_range = options[:block_range]
262
+ [block_range.first, block_range.last]
263
+ else
264
+ [options[:at_block_num], options[:until_block_num]]
265
+ end
266
+
267
+ loop do
268
+ break if !!until_block_num && !!at_block_num && until_block_num < at_block_num
269
+
270
+ database_api.get_dynamic_global_properties do |properties|
271
+ current_block_num = find_block_number(properties)
272
+ current_block_num = [current_block_num, until_block_num].compact.min
273
+ at_block_num ||= current_block_num
274
+
275
+ if current_block_num >= at_block_num
276
+ range = at_block_num..current_block_num
277
+
278
+ if object == :block_numbers
279
+ range.each do |n|
280
+ block.call n
281
+ block_interval = BLOCK_INTERVAL
282
+ end
283
+ else
284
+ block_api.send(object_method, block_range: range) do |b, n|
285
+ block.call b, n
286
+ block_interval = BLOCK_INTERVAL
287
+ end
288
+ end
289
+
290
+ at_block_num = range.max + 1
291
+ else
292
+ # The stream has stalled, so let's back off and let the node sync
293
+ # up. We'll catch up with a bigger batch in the next cycle.
294
+ block_interval = [block_interval * 2, MAX_BACKOFF_BLOCK_INTERVAL].min
295
+ end
296
+ end
297
+
298
+ sleep block_interval
299
+ end
300
+ end
301
+
302
+ # @private
303
+ def find_block_number(properties)
304
+ block_num = case mode
305
+ when :head then properties.head_block_number
306
+ when :irreversible then properties.last_irreversible_block_num
307
+ else; raise Crea::ArgumentError, "Unknown mode: #{mode}"
308
+ end
309
+
310
+ block_num
311
+ end
312
+
313
+ # @private
314
+ def transform_types(types)
315
+ [types].compact.flatten.map do |type|
316
+ type = type.to_s
317
+
318
+ unless type.end_with? '_operation'
319
+ warn "Op type #{type} is deprecated. Use #{type}_operation instead." unless @no_warn
320
+ type += '_operation'
321
+ end
322
+
323
+ type
324
+ end
325
+ end
326
+
327
+ # @private
328
+ def get_virtual_ops(types, block_num, block)
329
+ retries = 0
330
+
331
+ loop do
332
+ get_ops_in_block_options = case account_history_api
333
+ when Crea::CondenserApi
334
+ [block_num, true]
335
+ when Crea::AccountHistoryApi
336
+ {
337
+ block_num: block_num,
338
+ only_virtual: true
339
+ }
340
+ end
341
+
342
+ response = account_history_api.get_ops_in_block(*get_ops_in_block_options)
343
+
344
+ if response.nil? || (result = response.result).nil?
345
+ if retries < MAX_RETRY_COUNT
346
+ warn "Retrying get_ops_in_block on block #{block_num}" unless @no_warn
347
+ retries = retries + 1
348
+ sleep 9
349
+ redo
350
+ else
351
+ raise TooManyRetriesError, "unable to get valid result while finding virtual operations for block: #{block_num}"
352
+ end
353
+ end
354
+
355
+ ops = case account_history_api
356
+ when Crea::CondenserApi
357
+ result.map do |trx|
358
+ op = {type: trx.op[0] + '_operation', value: trx.op[1]}
359
+ op = Hashie::Mash.new(op)
360
+ end
361
+ when Crea::AccountHistoryApi then result.ops.map { |trx| trx.op }
362
+ end
363
+
364
+ if ops.empty?
365
+ if retries < MAX_RETRY_COUNT
366
+ sleep 3
367
+ retries = retries + 1
368
+ redo
369
+ else
370
+ warn "unable to find virtual operations for block: #{block_num}"
371
+ # raise TooManyRetriesError, "unable to find virtual operations for block: #{block_num}"
372
+ end
373
+ end
374
+
375
+ ops.each do |op|
376
+ next if types.any? && !types.include?(op.type)
377
+
378
+ block.call op, VOP_TRX_ID, block_num
379
+ end
380
+
381
+ break
382
+ end
383
+ end
384
+ end
385
+ end
@@ -0,0 +1,96 @@
1
+ module Crea
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 Crea::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