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.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +77 -0
- data/LICENSE +41 -0
- data/README.md +559 -0
- data/Rakefile +140 -0
- data/gource.sh +8 -0
- data/images/Anthony Martin.png +0 -0
- data/images/Marvin Hofmann.jpg +0 -0
- data/images/Marvin Hofmann.png +0 -0
- data/lib/bears.rb +17 -0
- data/lib/rubybear.rb +45 -0
- data/lib/rubybear/account_by_key_api.rb +7 -0
- data/lib/rubybear/account_history_api.rb +15 -0
- data/lib/rubybear/api.rb +907 -0
- data/lib/rubybear/base_error.rb +23 -0
- data/lib/rubybear/block_api.rb +14 -0
- data/lib/rubybear/broadcast_operations.json +500 -0
- data/lib/rubybear/chain.rb +299 -0
- data/lib/rubybear/chain_config.rb +22 -0
- data/lib/rubybear/chain_stats_api.rb +15 -0
- data/lib/rubybear/condenser_api.rb +99 -0
- data/lib/rubybear/database_api.rb +5 -0
- data/lib/rubybear/error_parser.rb +228 -0
- data/lib/rubybear/follow_api.rb +7 -0
- data/lib/rubybear/logger.rb +20 -0
- data/lib/rubybear/market_history_api.rb +19 -0
- data/lib/rubybear/methods.json +498 -0
- data/lib/rubybear/mixins/acts_as_poster.rb +124 -0
- data/lib/rubybear/mixins/acts_as_voter.rb +50 -0
- data/lib/rubybear/mixins/acts_as_wallet.rb +67 -0
- data/lib/rubybear/network_broadcast_api.rb +7 -0
- data/lib/rubybear/operation.rb +101 -0
- data/lib/rubybear/operation_ids.rb +98 -0
- data/lib/rubybear/operation_types.rb +139 -0
- data/lib/rubybear/stream.rb +527 -0
- data/lib/rubybear/tag_api.rb +33 -0
- data/lib/rubybear/transaction.rb +306 -0
- data/lib/rubybear/type/amount.rb +57 -0
- data/lib/rubybear/type/array.rb +17 -0
- data/lib/rubybear/type/beneficiaries.rb +29 -0
- data/lib/rubybear/type/future.rb +18 -0
- data/lib/rubybear/type/hash.rb +17 -0
- data/lib/rubybear/type/permission.rb +19 -0
- data/lib/rubybear/type/point_in_time.rb +19 -0
- data/lib/rubybear/type/price.rb +25 -0
- data/lib/rubybear/type/public_key.rb +18 -0
- data/lib/rubybear/type/serializer.rb +12 -0
- data/lib/rubybear/type/u_int16.rb +19 -0
- data/lib/rubybear/type/u_int32.rb +19 -0
- data/lib/rubybear/utils.rb +170 -0
- data/lib/rubybear/version.rb +4 -0
- data/rubybear.gemspec +40 -0
- 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,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
|