crea-ruby 0.0.1

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