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