rubybear 0.0.2

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 (54) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -0
  3. data/Gemfile.lock +77 -0
  4. data/LICENSE +41 -0
  5. data/README.md +559 -0
  6. data/Rakefile +140 -0
  7. data/gource.sh +8 -0
  8. data/images/Anthony Martin.png +0 -0
  9. data/images/Marvin Hofmann.jpg +0 -0
  10. data/images/Marvin Hofmann.png +0 -0
  11. data/lib/bears.rb +17 -0
  12. data/lib/rubybear.rb +45 -0
  13. data/lib/rubybear/account_by_key_api.rb +7 -0
  14. data/lib/rubybear/account_history_api.rb +15 -0
  15. data/lib/rubybear/api.rb +907 -0
  16. data/lib/rubybear/base_error.rb +23 -0
  17. data/lib/rubybear/block_api.rb +14 -0
  18. data/lib/rubybear/broadcast_operations.json +500 -0
  19. data/lib/rubybear/chain.rb +299 -0
  20. data/lib/rubybear/chain_config.rb +22 -0
  21. data/lib/rubybear/chain_stats_api.rb +15 -0
  22. data/lib/rubybear/condenser_api.rb +99 -0
  23. data/lib/rubybear/database_api.rb +5 -0
  24. data/lib/rubybear/error_parser.rb +228 -0
  25. data/lib/rubybear/follow_api.rb +7 -0
  26. data/lib/rubybear/logger.rb +20 -0
  27. data/lib/rubybear/market_history_api.rb +19 -0
  28. data/lib/rubybear/methods.json +498 -0
  29. data/lib/rubybear/mixins/acts_as_poster.rb +124 -0
  30. data/lib/rubybear/mixins/acts_as_voter.rb +50 -0
  31. data/lib/rubybear/mixins/acts_as_wallet.rb +67 -0
  32. data/lib/rubybear/network_broadcast_api.rb +7 -0
  33. data/lib/rubybear/operation.rb +101 -0
  34. data/lib/rubybear/operation_ids.rb +98 -0
  35. data/lib/rubybear/operation_types.rb +139 -0
  36. data/lib/rubybear/stream.rb +527 -0
  37. data/lib/rubybear/tag_api.rb +33 -0
  38. data/lib/rubybear/transaction.rb +306 -0
  39. data/lib/rubybear/type/amount.rb +57 -0
  40. data/lib/rubybear/type/array.rb +17 -0
  41. data/lib/rubybear/type/beneficiaries.rb +29 -0
  42. data/lib/rubybear/type/future.rb +18 -0
  43. data/lib/rubybear/type/hash.rb +17 -0
  44. data/lib/rubybear/type/permission.rb +19 -0
  45. data/lib/rubybear/type/point_in_time.rb +19 -0
  46. data/lib/rubybear/type/price.rb +25 -0
  47. data/lib/rubybear/type/public_key.rb +18 -0
  48. data/lib/rubybear/type/serializer.rb +12 -0
  49. data/lib/rubybear/type/u_int16.rb +19 -0
  50. data/lib/rubybear/type/u_int32.rb +19 -0
  51. data/lib/rubybear/utils.rb +170 -0
  52. data/lib/rubybear/version.rb +4 -0
  53. data/rubybear.gemspec +40 -0
  54. metadata +412 -0
@@ -0,0 +1,33 @@
1
+ module Rubybear
2
+ class TagApi < Api
3
+ def method_names
4
+ @method_names ||= [
5
+ :get_tags, # deprecated
6
+ :get_trending_tags,
7
+ :get_tags_used_by_author,
8
+ :get_discussion,
9
+ :get_content_replies,
10
+ :get_post_discussions_by_payout,
11
+ :get_comment_discussions_by_payout,
12
+ :get_discussions_by_trending,
13
+ :get_discussions_by_created,
14
+ :get_discussions_by_active,
15
+ :get_discussions_by_cashout,
16
+ :get_discussions_by_votes,
17
+ :get_discussions_by_children,
18
+ :get_discussions_by_hot,
19
+ :get_discussions_by_feed,
20
+ :get_discussions_by_blog,
21
+ :get_discussions_by_comments,
22
+ :get_discussions_by_promoted,
23
+ :get_replies_by_last_update,
24
+ :get_discussions_by_author_before_date,
25
+ :get_active_votes
26
+ ].freeze
27
+ end
28
+
29
+ def api_name
30
+ :tags_api
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,306 @@
1
+ require 'bitcoin'
2
+ require 'digest'
3
+ require 'time'
4
+
5
+ module Rubybear
6
+ # * graphenej:
7
+ # * https://github.com/kenCode-de/graphenej/blob/master/graphenej/src/main/java/de/bitsharesmunich/graphenej/Transaction.java#L142
8
+ class Transaction
9
+ include ChainConfig
10
+ include Utils
11
+
12
+ VALID_OPTIONS = %w(
13
+ wif private_key ref_block_num ref_block_prefix expiration
14
+ chain use_condenser_namespace
15
+ ).map(&:to_sym)
16
+ VALID_OPTIONS.each { |option| attr_accessor option }
17
+
18
+ def initialize(options = {})
19
+ options = options.dup
20
+ options.each do |k, v|
21
+ k = k.to_sym
22
+ if VALID_OPTIONS.include?(k.to_sym)
23
+ options.delete(k)
24
+ send("#{k}=", v)
25
+ end
26
+ end
27
+
28
+ @chain ||= :bears
29
+ @chain = @chain.to_sym
30
+ @chain_id = chain_id options[:chain_id]
31
+ @url = options[:url] || url
32
+ @operations = options[:operations] || []
33
+
34
+ @self_logger = false
35
+ @logger = if options[:logger].nil?
36
+ @self_logger = true
37
+ Rubybear.logger
38
+ else
39
+ options[:logger]
40
+ end
41
+
42
+ unless NETWORK_CHAIN_IDS.include? @chain_id
43
+ warning "Unknown chain id: #{@chain_id}"
44
+ end
45
+
46
+ if !!wif && !!private_key
47
+ raise TransactionError, "Do not pass both wif and private_key. That's confusing."
48
+ end
49
+
50
+ if !!wif
51
+ @private_key = Bitcoin::Key.from_base58 wif
52
+ end
53
+
54
+ @ref_block_num ||= nil
55
+ @ref_block_prefix ||= nil
56
+ @expiration ||= nil
57
+ @immutable_expiration = !!@expiration
58
+
59
+ options = options.merge(
60
+ url: @url,
61
+ chain: @chain,
62
+ pool_size: 1,
63
+ persist: false,
64
+ reuse_ssl_sessions: false
65
+ )
66
+
67
+ @api = Api.new(options)
68
+ @network_broadcast_api = NetworkBroadcastApi.new(options)
69
+
70
+ @use_condenser_namespace = if options.keys.include? :use_condenser_namespace
71
+ options[:use_condenser_namespace]
72
+ else
73
+ true
74
+ end
75
+
76
+ ObjectSpace.define_finalizer(self, self.class.finalize(@api, @network_broadcast_api, @self_logger, @logger))
77
+ end
78
+
79
+ def chain_id(chain_id = nil)
80
+ return chain_id if !!chain_id
81
+
82
+ case chain.to_s.downcase.to_sym
83
+ when :bears then NETWORKS_BEARS_CHAIN_ID
84
+ when :test then NETWORKS_TEST_CHAIN_ID
85
+ end
86
+ end
87
+
88
+ def url
89
+ case chain.to_s.downcase.to_sym
90
+ when :bears then NETWORKS_BEARS_DEFAULT_NODE
91
+ when :test then NETWORKS_TEST_DEFAULT_NODE
92
+ end
93
+ end
94
+
95
+ def process(broadcast = false)
96
+ prepare
97
+
98
+ if broadcast
99
+ loop do
100
+ response = broadcast_payload(payload)
101
+
102
+ if !!response.error
103
+ parser = ErrorParser.new(response)
104
+
105
+ if parser.can_reprepare?
106
+ debug "Error code: #{parser}, repreparing transaction ..."
107
+ prepare
108
+ redo
109
+ end
110
+ end
111
+
112
+ return response
113
+ end
114
+ else
115
+ self
116
+ end
117
+ ensure
118
+ shutdown
119
+ end
120
+
121
+ def operations
122
+ @operations = @operations.map do |op|
123
+ case op
124
+ when Operation then op
125
+ else; Operation.new(op)
126
+ end
127
+ end
128
+ end
129
+
130
+ def operations=(operations)
131
+ @operations = operations
132
+ end
133
+
134
+ def shutdown
135
+ @api.shutdown if !!@api
136
+ @network_broadcast_api.shutdown if !!@network_broadcast_api
137
+
138
+ if @self_logger
139
+ if !!@logger && defined?(@logger.close)
140
+ if defined?(@logger.closed?)
141
+ @logger.close unless @logger.closed?
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ def use_condenser_namespace?
148
+ !!@use_condenser_namespace
149
+ end
150
+ private
151
+ def broadcast_payload(payload)
152
+ if use_condenser_namespace?
153
+ @api.broadcast_transaction_synchronous(payload)
154
+ else
155
+ @network_broadcast_api.broadcast_transaction_synchronous(trx: payload)
156
+ end
157
+ end
158
+
159
+ def payload
160
+ @payload ||= {
161
+ expiration: @expiration.strftime('%Y-%m-%dT%H:%M:%S'),
162
+ ref_block_num: @ref_block_num,
163
+ ref_block_prefix: @ref_block_prefix,
164
+ operations: operations.map { |op| op.payload },
165
+ extensions: [],
166
+ signatures: [hexlify(signature)]
167
+ }
168
+ end
169
+
170
+ def prepare
171
+ raise TransactionError, "No wif or private key." unless !!@wif || !!@private_key
172
+
173
+ @payload = nil
174
+
175
+ while @expiration.nil? && @ref_block_num.nil? && @ref_block_prefix.nil?
176
+ @api.get_dynamic_global_properties do |properties, error|
177
+ if !!error
178
+ raise TransactionError, "Unable to prepare transaction.", error
179
+ end
180
+
181
+ @properties = properties
182
+ end
183
+
184
+ # You can actually go back as far as the TaPoS buffer will allow, which
185
+ # is something like 50,000 blocks.
186
+
187
+ block_number = @properties.last_irreversible_block_num
188
+
189
+ @api.get_block(block_number) do |block, error|
190
+ if !!error
191
+ ap error if defined?(ap) && ENV['DEBUG'] == 'true'
192
+ raise TransactionError, "Unable to prepare transaction: #{error.message || 'Unknown cause.'}"
193
+ end
194
+
195
+ if !!block && !!block.previous
196
+ @ref_block_num = (block_number - 1) & 0xFFFF
197
+ @ref_block_prefix = unhexlify(block.previous[8..-1]).unpack('V*')[0]
198
+
199
+ # The expiration allows for transactions to expire if they are not
200
+ # included into a block by that time. Always update it to the current
201
+ # time + EXPIRE_IN_SECS.
202
+ #
203
+ # Note, as of #1215, expiration exactly 'now' will be rejected:
204
+ # https://github.com/bearshares/bears/blob/57451b80d2cf480dcce9b399e48e56aa7af1d818/libraries/chain/database.cpp#L2870
205
+ # https://github.com/bearshares/bears/issues/1215
206
+
207
+ block_time = Time.parse(@properties.time + 'Z')
208
+ @expiration ||= block_time + EXPIRE_IN_SECS
209
+ else
210
+ # Suspect this happens when there are microforks, but it should be
211
+ # rare, especially since we're asking for the last irreversible
212
+ # block.
213
+
214
+ if block.nil?
215
+ warning "Block missing while trying to prepare transaction, retrying ..."
216
+ else
217
+ debug block if %w(DEBUG TRACE).include? ENV['LOG']
218
+
219
+ warning "Block structure while trying to prepare transaction, retrying ..."
220
+ end
221
+
222
+ @expiration = nil unless @immutable_expiration
223
+ end
224
+ end
225
+ end
226
+
227
+ self
228
+ end
229
+
230
+ def to_bytes
231
+ bytes = unhexlify(@chain_id)
232
+ bytes << pakS(@ref_block_num)
233
+ bytes << pakI(@ref_block_prefix)
234
+ bytes << pakI(@expiration.to_i)
235
+ bytes << pakC(operations.size)
236
+
237
+ operations.each do |op|
238
+ bytes << op.to_bytes
239
+ end
240
+
241
+ bytes << 0x00 # extensions
242
+
243
+ bytes
244
+ end
245
+
246
+ def digest
247
+ Digest::SHA256.digest(to_bytes)
248
+ end
249
+
250
+ # May not find all non-canonicals, see: https://github.com/lian/bitcoin-ruby/issues/196
251
+ def signature
252
+ public_key_hex = @private_key.pub
253
+ ec = Bitcoin::OpenSSL_EC
254
+ digest_hex = digest.freeze
255
+ count = 0
256
+
257
+ loop do
258
+ count += 1
259
+ debug "#{count} attempts to find canonical signature" if count % 40 == 0
260
+ sig = ec.sign_compact(digest_hex, @private_key.priv, public_key_hex, false)
261
+
262
+ next if public_key_hex != ec.recover_compact(digest_hex, sig)
263
+
264
+ return sig if canonical? sig
265
+ end
266
+ end
267
+
268
+ # See: https://github.com/bearshares/bears/issues/1944
269
+ def canonical?(sig)
270
+ sig = sig.unpack('C*')
271
+
272
+ !(
273
+ ((sig[0] & 0x80 ) != 0) || ( sig[0] == 0 ) ||
274
+ ((sig[1] & 0x80 ) != 0) ||
275
+ ((sig[32] & 0x80 ) != 0) || ( sig[32] == 0 ) ||
276
+ ((sig[33] & 0x80 ) != 0)
277
+ )
278
+ end
279
+
280
+ def self.finalize(api, network_broadcast_api, self_logger, logger)
281
+ proc {
282
+ if !!api && !api.stopped?
283
+ puts "DESTROY: #{api.inspect}" if ENV['LOG'] == 'TRACE'
284
+ api.shutdown
285
+ api = nil
286
+ end
287
+
288
+ if !!network_broadcast_api && !network_broadcast_api.stopped?
289
+ puts "DESTROY: #{network_broadcast_api.inspect}" if ENV['LOG'] == 'TRACE'
290
+ network_broadcast_api.shutdown
291
+ network_broadcast_api = nil
292
+ end
293
+
294
+ begin
295
+ if self_logger
296
+ if !!logger && defined?(logger.close)
297
+ if defined?(logger.closed?)
298
+ logger.close unless logger.closed?
299
+ end
300
+ end
301
+ end
302
+ rescue IOError, NoMethodError => _; end
303
+ }
304
+ end
305
+ end
306
+ end
@@ -0,0 +1,57 @@
1
+ module Rubybear
2
+ module Type
3
+
4
+ # See: https://github.com/xeroc/piston-lib/blob/34a7525cee119ec9b24a99577ede2d54466fca0e/bearsbase/operations.py
5
+ class Amount < Serializer
6
+ def initialize(value)
7
+ super(:amount, value)
8
+
9
+ case value
10
+ when ::Array
11
+ a, p, t = value
12
+ @asset = case t
13
+ when '@@000000013' then 'BSD'
14
+ when '@@000000021' then 'BEARS'
15
+ when '@@000000037' then 'COINS'
16
+ else; raise TypeError, "Asset #{@asset} unknown."
17
+ end
18
+ @precision = p
19
+ @amount = "%.#{p}f" % (a.to_f / 10 ** p)
20
+ else
21
+ @amount, @asset = value.strip.split(' ')
22
+ @precision = case @asset
23
+ when 'BEARS' then 3
24
+ when 'COINS' then 6
25
+ when 'BSD' then 3
26
+ when 'CORE' then 3
27
+ when 'CESTS' then 6
28
+ when 'TEST' then 3
29
+ else; raise TypeError, "Asset #{@asset} unknown."
30
+ end
31
+ end
32
+ end
33
+
34
+ def to_bytes
35
+ asset = @asset.ljust(7, "\x00")
36
+ amount = (@amount.to_f * 10 ** @precision).round
37
+
38
+ [amount].pack('q') +
39
+ [@precision].pack('c') +
40
+ asset
41
+ end
42
+
43
+ def to_a
44
+ case @asset
45
+ when 'BEARS' then [(@amount.to_f * 1000).to_i.to_s, 3, '@@000000021']
46
+ when 'COINS' then [(@amount.to_f * 1000000).to_i.to_s, 6, '@@000000037']
47
+ when 'BSD' then [(@amount.to_f * 1000).to_i.to_s, 3, '@@000000013']
48
+ else; raise TypeError, "Asset #{@asset} unknown."
49
+ end
50
+ end
51
+
52
+ def to_s
53
+ "#{@amount} #{@asset}"
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,17 @@
1
+ module Rubybear
2
+ module Type
3
+ class Array < Serializer
4
+ def initialize(value)
5
+ super(:array, true)
6
+ end
7
+
8
+ def to_bytes
9
+ pakArr(@value)
10
+ end
11
+
12
+ def to_s
13
+ @value.to_json
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,29 @@
1
+ module Rubybear
2
+ module Type
3
+ class Beneficiaries < Serializer
4
+
5
+ def initialize(value)
6
+ super(:beneficiaries, value)
7
+ end
8
+
9
+ def to_bytes
10
+ pakArr([]) + pakHash(@value)
11
+ end
12
+
13
+ def to_h
14
+ v = @value.map do |b|
15
+ case b
16
+ when ::Array then {account: b.first, weight: b.last}
17
+ else; {account: b.keys.first, weight: b.values.first}
18
+ end
19
+ end
20
+
21
+ {@key => v}
22
+ end
23
+
24
+ def to_s
25
+ to_h.to_json
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,18 @@
1
+ module Rubybear
2
+ module Type
3
+
4
+ # See: https://github.com/xeroc/piston-lib/blob/34a7525cee119ec9b24a99577ede2d54466fca0e/bearsbase/operations.py
5
+ class Future < Serializer
6
+ def initialize(value)
7
+ super(:future, true)
8
+ end
9
+
10
+ def to_bytes
11
+ [1].pack('U')
12
+ end
13
+
14
+ def to_s
15
+ end
16
+ end
17
+ end
18
+ end