rubybear 0.0.2

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