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,299 @@
|
|
1
|
+
module Rubybear
|
2
|
+
# Examples ...
|
3
|
+
#
|
4
|
+
# To vote on a post/comment:
|
5
|
+
#
|
6
|
+
# bears = Rubybear::Chain.new(chain: :bears, account_name: 'your account name', wif: 'your wif')
|
7
|
+
# bears.vote!(10000, 'author', 'post-or-comment-permlink')
|
8
|
+
#
|
9
|
+
# To post and vote in the same transaction:
|
10
|
+
#
|
11
|
+
# bears = Rubybear::Chain.new(chain: :bears, account_name: 'your account name', wif: 'your wif')
|
12
|
+
# bears.post!(title: 'title of my post', body: 'body of my post', tags: ['tag'], self_upvote: 10000)
|
13
|
+
#
|
14
|
+
# To post and vote with declined payout:
|
15
|
+
#
|
16
|
+
# bears = Rubybear::Chain.new(chain: :bears, account_name: 'your account name', wif: 'your wif')
|
17
|
+
#
|
18
|
+
# options = {
|
19
|
+
# title: 'title of my post',
|
20
|
+
# body: 'body of my post',
|
21
|
+
# tags: ['tag'],
|
22
|
+
# self_upvote: 10000,
|
23
|
+
# percent_bears_dollars: 0
|
24
|
+
# }
|
25
|
+
#
|
26
|
+
# bears.post!(options)
|
27
|
+
#
|
28
|
+
class Chain
|
29
|
+
include Mixins::ActsAsPoster
|
30
|
+
include Mixins::ActsAsVoter
|
31
|
+
include Mixins::ActsAsWallet
|
32
|
+
|
33
|
+
VALID_OPTIONS = %w(
|
34
|
+
chain account_name wif url failover_urls
|
35
|
+
).map(&:to_sym)
|
36
|
+
VALID_OPTIONS.each { |option| attr_accessor option }
|
37
|
+
|
38
|
+
def self.parse_slug(*args)
|
39
|
+
args = [args].flatten
|
40
|
+
|
41
|
+
if args.size == 1
|
42
|
+
case args[0]
|
43
|
+
when String then split_slug(args[0])
|
44
|
+
when ::Hash then [args[0]['author'], args[0]['permlink']]
|
45
|
+
end
|
46
|
+
else
|
47
|
+
args
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def initialize(options = {})
|
52
|
+
options = options.dup
|
53
|
+
options.each do |k, v|
|
54
|
+
k = k.to_sym
|
55
|
+
if VALID_OPTIONS.include?(k.to_sym)
|
56
|
+
options.delete(k)
|
57
|
+
send("#{k}=", v)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
@account_name ||= ENV['ACCOUNT_NAME']
|
62
|
+
@wif ||= ENV['WIF']
|
63
|
+
|
64
|
+
reset
|
65
|
+
end
|
66
|
+
|
67
|
+
# Find a specific block by block number.
|
68
|
+
#
|
69
|
+
# Example:
|
70
|
+
#
|
71
|
+
# bears = Rubybear::Chain.new(chain: :bears)
|
72
|
+
# block = bears.find_block(12345678)
|
73
|
+
# transactions = block.transactions
|
74
|
+
#
|
75
|
+
# @param block_number [Fixnum]
|
76
|
+
# @return [::Hash]
|
77
|
+
def find_block(block_number)
|
78
|
+
api.get_blocks(block_number).first
|
79
|
+
end
|
80
|
+
|
81
|
+
# Find a specific account by name.
|
82
|
+
#
|
83
|
+
# Example:
|
84
|
+
#
|
85
|
+
# bears = Rubybear::Chain.new(chain: :bears)
|
86
|
+
# ned = bears.find_account('ned')
|
87
|
+
# coining_shares = ned.coining_shares
|
88
|
+
#
|
89
|
+
# @param account_name [String] Name of the account to find.
|
90
|
+
# @return [::Hash]
|
91
|
+
def find_account(account_name)
|
92
|
+
api.get_accounts([account_name]) do |accounts, err|
|
93
|
+
raise ChainError, ErrorParser.new(err) if !!err
|
94
|
+
|
95
|
+
accounts[0]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Find a specific comment by author and permlink or slug.
|
100
|
+
#
|
101
|
+
# Example:
|
102
|
+
#
|
103
|
+
# bears = Rubybear::Chain.new(chain: :bears)
|
104
|
+
# comment = bears.find_comment('inertia', 'kinda-spooky') # by account, permlink
|
105
|
+
# active_votes = comment.active_votes
|
106
|
+
#
|
107
|
+
# ... or ...
|
108
|
+
#
|
109
|
+
# comment = bears.find_comment('@inertia/kinda-spooky') # by slug
|
110
|
+
#
|
111
|
+
# @param args [String || ::Array<String>] Slug or author, permlink of comment.
|
112
|
+
# @return [::Hash]
|
113
|
+
def find_comment(*args)
|
114
|
+
author, permlink = Chain.parse_slug(args)
|
115
|
+
|
116
|
+
api.get_content(author, permlink) do |comment, err|
|
117
|
+
raise ChainError, ErrorParser.new(err) if !!err
|
118
|
+
|
119
|
+
comment unless comment.id == 0
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Current dynamic global properties, cached for 3 seconds. This is useful
|
124
|
+
# for reading properties without worrying about actually fetching it over
|
125
|
+
# rpc more than needed.
|
126
|
+
def properties
|
127
|
+
@properties ||= nil
|
128
|
+
|
129
|
+
if !!@properties && Time.now.utc - Time.parse(@properties.time + 'Z') > 3
|
130
|
+
@properties = nil
|
131
|
+
end
|
132
|
+
|
133
|
+
return @properties if !!@properties
|
134
|
+
|
135
|
+
api.get_dynamic_global_properties do |properties|
|
136
|
+
@properties = properties
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def block_time
|
141
|
+
Time.parse(properties.time + 'Z')
|
142
|
+
end
|
143
|
+
|
144
|
+
# Returns the current base (e.g. BEARS) price in the coin asset (e.g.
|
145
|
+
# COINS).
|
146
|
+
#
|
147
|
+
def base_per_mcoin
|
148
|
+
total_coining_fund_bears = properties.total_coining_fund_bears.to_f
|
149
|
+
total_coining_shares_mcoin = properties.total_coining_shares.to_f / 1e6
|
150
|
+
|
151
|
+
total_coining_fund_bears / total_coining_shares_mcoin
|
152
|
+
end
|
153
|
+
|
154
|
+
# Returns the current base (e.g. BEARS) price in the debt asset (e.g BSD).
|
155
|
+
#
|
156
|
+
def base_per_debt
|
157
|
+
api.get_feed_history do |feed_history|
|
158
|
+
current_median_history = feed_history.current_median_history
|
159
|
+
base = current_median_history.base
|
160
|
+
base = base.split(' ').first.to_f
|
161
|
+
quote = current_median_history.quote
|
162
|
+
quote = quote.split(' ').first.to_f
|
163
|
+
|
164
|
+
(base / quote) * base_per_mcoin
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# List of accounts followed by account.
|
169
|
+
#
|
170
|
+
# @param account_name String Name of the account.
|
171
|
+
# @return [::Array<String>]
|
172
|
+
def followed_by(account_name)
|
173
|
+
return [] if account_name.nil?
|
174
|
+
|
175
|
+
followers = []
|
176
|
+
count = -1
|
177
|
+
|
178
|
+
until count == followers.size
|
179
|
+
count = followers.size
|
180
|
+
follow_api.get_followers(account: account_name, start: followers.last, type: 'blog', limit: 1000) do |follows, err|
|
181
|
+
raise ChainError, ErrorParser.new(err) if !!err
|
182
|
+
|
183
|
+
followers += follows.map(&:follower)
|
184
|
+
followers = followers.uniq
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
followers
|
189
|
+
end
|
190
|
+
|
191
|
+
# List of accounts following account.
|
192
|
+
#
|
193
|
+
# @param account_name String Name of the account.
|
194
|
+
# @return [::Array<String>]
|
195
|
+
def following(account_name)
|
196
|
+
return [] if account_name.nil?
|
197
|
+
|
198
|
+
following = []
|
199
|
+
count = -1
|
200
|
+
|
201
|
+
until count == following.size
|
202
|
+
count = following.size
|
203
|
+
follow_api.get_following(account: account_name, start: following.last, type: 'blog', limit: 100) do |follows, err|
|
204
|
+
raise ChainError, ErrorParser.new(err) if !!err
|
205
|
+
|
206
|
+
following += follows.map(&:following)
|
207
|
+
following = following.uniq
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
following
|
212
|
+
end
|
213
|
+
|
214
|
+
# Clears out queued properties.
|
215
|
+
def reset_properties
|
216
|
+
@properties = nil
|
217
|
+
end
|
218
|
+
|
219
|
+
# Clears out queued operations.
|
220
|
+
def reset_operations
|
221
|
+
@operations = []
|
222
|
+
end
|
223
|
+
|
224
|
+
# Clears out all properties and operations.
|
225
|
+
def reset
|
226
|
+
reset_properties
|
227
|
+
reset_operations
|
228
|
+
|
229
|
+
@api = @block_api = @follow_api = nil
|
230
|
+
end
|
231
|
+
|
232
|
+
# Broadcast queued operations.
|
233
|
+
#
|
234
|
+
# @param auto_reset [boolean] clears operations no matter what, even if there's an error.
|
235
|
+
def broadcast!(auto_reset = false)
|
236
|
+
raise ChainError, "Required option: chain" if @chain.nil?
|
237
|
+
raise ChainError, "Required option: account_name, wif" if @account_name.nil? || @wif.nil?
|
238
|
+
|
239
|
+
begin
|
240
|
+
transaction = Rubybear::Transaction.new(build_options)
|
241
|
+
transaction.operations = @operations
|
242
|
+
response = transaction.process(true)
|
243
|
+
rescue => e
|
244
|
+
reset if auto_reset
|
245
|
+
raise e
|
246
|
+
end
|
247
|
+
|
248
|
+
if !!response.result
|
249
|
+
reset
|
250
|
+
response
|
251
|
+
else
|
252
|
+
reset if auto_reset
|
253
|
+
ErrorParser.new(response)
|
254
|
+
end
|
255
|
+
end
|
256
|
+
private
|
257
|
+
def self.split_slug(slug)
|
258
|
+
slug = slug.split('@').last
|
259
|
+
author = slug.split('/')[0]
|
260
|
+
permlink = slug.split('/')[1..-1].join('/')
|
261
|
+
permlink = permlink.split('#')[0]
|
262
|
+
|
263
|
+
[author, permlink]
|
264
|
+
end
|
265
|
+
|
266
|
+
def build_options
|
267
|
+
{
|
268
|
+
chain: chain,
|
269
|
+
wif: wif,
|
270
|
+
url: url,
|
271
|
+
failover_urls: failover_urls
|
272
|
+
}
|
273
|
+
end
|
274
|
+
|
275
|
+
def api
|
276
|
+
@api ||= Api.new(build_options)
|
277
|
+
end
|
278
|
+
|
279
|
+
def block_api
|
280
|
+
@block_api ||= BlockApi.new(build_options)
|
281
|
+
end
|
282
|
+
|
283
|
+
def follow_api
|
284
|
+
@follow_api ||= FollowApi.new(build_options)
|
285
|
+
end
|
286
|
+
|
287
|
+
def default_max_acepted_payout
|
288
|
+
"1000000.000 #{default_debt_asset}"
|
289
|
+
end
|
290
|
+
|
291
|
+
def default_debt_asset
|
292
|
+
case chain
|
293
|
+
when :bears then ChainConfig::NETWORKS_BEARS_DEBT_ASSET
|
294
|
+
when :test then ChainConfig::NETWORKS_TEST_DEBT_ASSET
|
295
|
+
else; raise ChainError, "Unknown chain: #{chain}"
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Rubybear
|
2
|
+
module ChainConfig
|
3
|
+
EXPIRE_IN_SECS = 600
|
4
|
+
EXPIRE_IN_SECS_PROPOSAL = 24 * 60 * 60
|
5
|
+
|
6
|
+
NETWORKS_BEARS_CHAIN_ID = 'b510834141c312c2aa8837040734605f2333f1ecc4f634576372f9c12dc7e8b2'
|
7
|
+
NETWORKS_BEARS_ADDRESS_PREFIX = 'SHR'
|
8
|
+
NETWORKS_BEARS_CORE_ASSET = 'BEARS'
|
9
|
+
NETWORKS_BEARS_DEBT_ASSET = 'BSD'
|
10
|
+
NETWORKS_BEARS_COIN_ASSET = 'COINS'
|
11
|
+
NETWORKS_BEARS_DEFAULT_NODE = 'https://api.bearshares.com'
|
12
|
+
|
13
|
+
NETWORKS_TEST_CHAIN_ID = '18dcf0a285365fc58b71f18b3d3fec954aa0c141c44e4e5cb4cf777b9eab274e'
|
14
|
+
NETWORKS_TEST_ADDRESS_PREFIX = 'TST'
|
15
|
+
NETWORKS_TEST_CORE_ASSET = 'CORE'
|
16
|
+
NETWORKS_TEST_DEBT_ASSET = 'TEST'
|
17
|
+
NETWORKS_TEST_COIN_ASSET = 'CESTS'
|
18
|
+
NETWORKS_TEST_DEFAULT_NODE = 'https://test.bears.ws'
|
19
|
+
|
20
|
+
NETWORK_CHAIN_IDS = [NETWORKS_BEARS_CHAIN_ID, NETWORKS_TEST_CHAIN_ID]
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Rubybear
|
2
|
+
class CondenserApi < Api
|
3
|
+
METHOD_NAMES = [
|
4
|
+
:broadcast_block,
|
5
|
+
:broadcast_transaction,
|
6
|
+
:broadcast_transaction_synchronous,
|
7
|
+
:get_account_bandwidth,
|
8
|
+
:get_account_count,
|
9
|
+
:get_account_history,
|
10
|
+
:get_account_references,
|
11
|
+
:get_account_reputations,
|
12
|
+
:get_account_votes,
|
13
|
+
:get_accounts,
|
14
|
+
:get_active_votes,
|
15
|
+
:get_active_witnesses,
|
16
|
+
:get_block,
|
17
|
+
:get_block_header,
|
18
|
+
:get_blog,
|
19
|
+
:get_blog_authors,
|
20
|
+
:get_blog_entries,
|
21
|
+
:get_chain_properties,
|
22
|
+
:get_comment_discussions_by_payout,
|
23
|
+
:get_config,
|
24
|
+
:get_content,
|
25
|
+
:get_content_replies,
|
26
|
+
:get_conversion_requests,
|
27
|
+
:get_current_median_history_price,
|
28
|
+
:get_discussions_by_active,
|
29
|
+
:get_discussions_by_author_before_date,
|
30
|
+
:get_discussions_by_blog,
|
31
|
+
:get_discussions_by_cashout,
|
32
|
+
:get_discussions_by_children,
|
33
|
+
:get_discussions_by_comments,
|
34
|
+
:get_discussions_by_created,
|
35
|
+
:get_discussions_by_feed,
|
36
|
+
:get_discussions_by_hot,
|
37
|
+
:get_discussions_by_promoted,
|
38
|
+
:get_discussions_by_trending,
|
39
|
+
:get_discussions_by_votes,
|
40
|
+
:get_dynamic_global_properties,
|
41
|
+
:get_escrow,
|
42
|
+
:get_expiring_coining_delegations,
|
43
|
+
:get_feed,
|
44
|
+
:get_feed_entries,
|
45
|
+
:get_feed_history,
|
46
|
+
:get_follow_count,
|
47
|
+
:get_followers,
|
48
|
+
:get_following,
|
49
|
+
:get_hardfork_version,
|
50
|
+
:get_key_references,
|
51
|
+
:get_market_history,
|
52
|
+
:get_market_history_buckets,
|
53
|
+
:get_next_scheduled_hardfork,
|
54
|
+
:get_open_orders,
|
55
|
+
:get_ops_in_block,
|
56
|
+
:get_order_book,
|
57
|
+
:get_owner_history,
|
58
|
+
:get_post_discussions_by_payout,
|
59
|
+
:get_potential_signatures,
|
60
|
+
:get_reblogged_by,
|
61
|
+
:get_recent_trades,
|
62
|
+
:get_recovery_request,
|
63
|
+
:get_replies_by_last_update,
|
64
|
+
:get_required_signatures,
|
65
|
+
:get_reward_fund,
|
66
|
+
:get_savings_withdraw_from,
|
67
|
+
:get_savings_withdraw_to,
|
68
|
+
:get_state,
|
69
|
+
:get_tags_used_by_author,
|
70
|
+
:get_ticker,
|
71
|
+
:get_trade_history,
|
72
|
+
:get_transaction,
|
73
|
+
:get_transaction_hex,
|
74
|
+
:get_trending_tags,
|
75
|
+
:get_version,
|
76
|
+
:get_coining_delegations,
|
77
|
+
:get_volume,
|
78
|
+
:get_withdraw_routes,
|
79
|
+
:get_witness_by_account,
|
80
|
+
:get_witness_count,
|
81
|
+
:get_witness_schedule,
|
82
|
+
:get_witnesses,
|
83
|
+
:get_witnesses_by_vote,
|
84
|
+
:lookup_account_names,
|
85
|
+
:lookup_accounts,
|
86
|
+
:lookup_witness_accounts,
|
87
|
+
:verify_account_authority,
|
88
|
+
:verify_authority
|
89
|
+
].freeze
|
90
|
+
|
91
|
+
def method_names
|
92
|
+
METHOD_NAMES
|
93
|
+
end
|
94
|
+
|
95
|
+
def api_name
|
96
|
+
:condenser_api
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,228 @@
|
|
1
|
+
module Rubybear
|
2
|
+
class ErrorParser
|
3
|
+
include Utils
|
4
|
+
|
5
|
+
attr_reader :response, :error, :error_code, :error_message,
|
6
|
+
:api_name, :api_method, :api_params,
|
7
|
+
:expiry, :can_retry, :can_reprepare, :node_degraded, :trx_id, :debug
|
8
|
+
|
9
|
+
alias expiry? expiry
|
10
|
+
alias can_retry? can_retry
|
11
|
+
alias can_reprepare? can_reprepare
|
12
|
+
alias node_degraded? node_degraded
|
13
|
+
|
14
|
+
REPREPARE_WHITELIST = [
|
15
|
+
'is_canonical( c ): signature is not canonical',
|
16
|
+
'now < trx.expiration: '
|
17
|
+
]
|
18
|
+
|
19
|
+
DUPECHECK = '(skip & skip_transaction_dupe_check) || trx_idx.indices().get<by_trx_id>().find(trx_id) == trx_idx.indices().get<by_trx_id>().end(): Duplicate transaction check failed'
|
20
|
+
|
21
|
+
REPREPARE_BLACKLIST = [DUPECHECK]
|
22
|
+
|
23
|
+
def initialize(response)
|
24
|
+
@response = response
|
25
|
+
|
26
|
+
@error = nil
|
27
|
+
@error_code = nil
|
28
|
+
@error_message = nil
|
29
|
+
@api_name = nil
|
30
|
+
@api_method = nil
|
31
|
+
@api_params = nil
|
32
|
+
|
33
|
+
@expiry = nil
|
34
|
+
@can_retry = nil
|
35
|
+
@can_reprepare = nil
|
36
|
+
@trx_id = nil
|
37
|
+
@debug = nil
|
38
|
+
|
39
|
+
parse_error_response
|
40
|
+
end
|
41
|
+
|
42
|
+
def parse_error_response
|
43
|
+
if response.nil?
|
44
|
+
@expiry = false
|
45
|
+
@can_retry = false
|
46
|
+
@can_reprepare = false
|
47
|
+
|
48
|
+
return
|
49
|
+
end
|
50
|
+
|
51
|
+
@response = JSON[response] if response.class == String
|
52
|
+
|
53
|
+
@error = if !!@response['error']
|
54
|
+
response['error']
|
55
|
+
else
|
56
|
+
response
|
57
|
+
end
|
58
|
+
|
59
|
+
begin
|
60
|
+
if !!@error['data']
|
61
|
+
# These are, by far, the more interesting errors, so we try to pull
|
62
|
+
# them out first, if possible.
|
63
|
+
|
64
|
+
@error_code = @error['data']['code']
|
65
|
+
stacks = @error['data']['stack']
|
66
|
+
stack_formats = nil
|
67
|
+
|
68
|
+
@error_message = if !!stacks
|
69
|
+
stack_formats = stacks.map { |s| s['format'] }
|
70
|
+
stack_datum = stacks.map { |s| s['data'] }
|
71
|
+
data_call_method = stack_datum.find { |data| data['call.method'] == 'call' }
|
72
|
+
data_name = stack_datum.find { |data| !!data['name'] }
|
73
|
+
|
74
|
+
# See if we can recover a transaction id out of this hot mess.
|
75
|
+
data_trx_ix = stack_datum.find { |data| !!data['trx_ix'] }
|
76
|
+
@trx_id = data_trx_ix['trx_ix'] if !!data_trx_ix
|
77
|
+
|
78
|
+
stack_formats.reject(&:empty?).join('; ')
|
79
|
+
else
|
80
|
+
@error_code ||= @error['code']
|
81
|
+
@error['message']
|
82
|
+
end
|
83
|
+
|
84
|
+
@api_name, @api_method, @api_params = if !!data_call_method
|
85
|
+
@api_name = data_call_method['call.params']
|
86
|
+
end
|
87
|
+
else
|
88
|
+
@error_code = @error['code']
|
89
|
+
@error_message = @error['message']
|
90
|
+
@expiry = false
|
91
|
+
@can_retry = false
|
92
|
+
@can_reprepare = false
|
93
|
+
end
|
94
|
+
|
95
|
+
case @error_code
|
96
|
+
when -32603
|
97
|
+
if error_match?('Internal Error')
|
98
|
+
@expiry = false
|
99
|
+
@can_retry = true
|
100
|
+
@can_reprepare = true
|
101
|
+
end
|
102
|
+
when -32003
|
103
|
+
if error_match?('Unable to acquire database lock')
|
104
|
+
@expiry = false
|
105
|
+
@can_retry = true
|
106
|
+
@can_reprepare = true
|
107
|
+
end
|
108
|
+
when -32000
|
109
|
+
@expiry = false
|
110
|
+
@can_retry = coerce_backtrace
|
111
|
+
@can_reprepare = if @api_name == 'network_broadcast_api'
|
112
|
+
error_match(REPREPARE_WHITELIST)
|
113
|
+
else
|
114
|
+
false
|
115
|
+
end
|
116
|
+
when 10
|
117
|
+
@expiry = false
|
118
|
+
@can_retry = coerce_backtrace
|
119
|
+
@can_reprepare = !!stack_formats && (stack_formats & REPREPARE_WHITELIST).any?
|
120
|
+
when 13
|
121
|
+
@error_message = @error['data']['message']
|
122
|
+
@expiry = false
|
123
|
+
@can_retry = false
|
124
|
+
@can_reprepare = false
|
125
|
+
when 3030000
|
126
|
+
@error_message = @error['data']['message']
|
127
|
+
@expiry = false
|
128
|
+
@can_retry = false
|
129
|
+
@can_reprepare = false
|
130
|
+
when 4030100
|
131
|
+
# Code 4030100 is "transaction_expiration_exception: transaction
|
132
|
+
# expiration exception". If we assume the expiration was valid, the
|
133
|
+
# node might be bad and needs to be dropped.
|
134
|
+
|
135
|
+
@expiry = true
|
136
|
+
@can_retry = true
|
137
|
+
@can_reprepare = false
|
138
|
+
when 4030200
|
139
|
+
# Code 4030200 is "transaction tapos exception". They are recoverable
|
140
|
+
# if the transaction hasn't expired yet. A tapos exception can be
|
141
|
+
# retried in situations where the node is behind and the tapos is
|
142
|
+
# based on a block the node doesn't know about yet.
|
143
|
+
|
144
|
+
@expiry = false
|
145
|
+
@can_retry = true
|
146
|
+
|
147
|
+
# Allow fall back to reprepare if retry fails.
|
148
|
+
@can_reprepare = true
|
149
|
+
else
|
150
|
+
@expiry = false
|
151
|
+
@can_retry = false
|
152
|
+
@can_reprepare = false
|
153
|
+
end
|
154
|
+
rescue => e
|
155
|
+
if defined? ap
|
156
|
+
if ENV['DEBUG'] == 'true'
|
157
|
+
ap error_parser_exception: e, original_response: response, backtrace: e.backtrace
|
158
|
+
else
|
159
|
+
ap error_parser_exception: e, original_response: response
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
@expiry = false
|
164
|
+
@can_retry = false
|
165
|
+
@can_reprepare = false
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def coerce_backtrace
|
170
|
+
can_retry = false
|
171
|
+
|
172
|
+
case @error['code']
|
173
|
+
when -32003
|
174
|
+
any_of = [
|
175
|
+
'Internal Error"',
|
176
|
+
'_api_plugin not enabled.'
|
177
|
+
]
|
178
|
+
|
179
|
+
can_retry = error_match?('Unable to acquire database lock')
|
180
|
+
|
181
|
+
if !can_retry && error_match?(any_of)
|
182
|
+
can_retry = true
|
183
|
+
@node_degraded = true
|
184
|
+
else
|
185
|
+
@node_degraded = false
|
186
|
+
end
|
187
|
+
when -32002
|
188
|
+
can_retry = @node_degraded = error_match?('Could not find API')
|
189
|
+
when 1
|
190
|
+
can_retry = @node_degraded = error_match?('no method with name \'condenser_api')
|
191
|
+
end
|
192
|
+
|
193
|
+
can_retry
|
194
|
+
end
|
195
|
+
|
196
|
+
def error_match?(matches)
|
197
|
+
matches = [matches].flatten
|
198
|
+
|
199
|
+
any = matches.map do |match|
|
200
|
+
case match
|
201
|
+
when String
|
202
|
+
@error['message'] && @error['message'].include?(match)
|
203
|
+
when ::Array
|
204
|
+
if @error['message']
|
205
|
+
match.map { |m| m.include?(match) }.include? true
|
206
|
+
else
|
207
|
+
false
|
208
|
+
end
|
209
|
+
else; false
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
any.include?(true)
|
214
|
+
end
|
215
|
+
|
216
|
+
def to_s
|
217
|
+
if !!error_message && !error_message.empty?
|
218
|
+
"#{error_code}: #{error_message}"
|
219
|
+
else
|
220
|
+
error_code.to_s
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def inspect
|
225
|
+
"#<#{self.class.name} [#{to_s}]>"
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|