voilkruby 0.0.1
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 +519 -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/voilk.rb +17 -0
- data/lib/voilkruby.rb +45 -0
- data/lib/voilkruby/account_by_key_api.rb +7 -0
- data/lib/voilkruby/account_history_api.rb +15 -0
- data/lib/voilkruby/api.rb +772 -0
- data/lib/voilkruby/base_error.rb +23 -0
- data/lib/voilkruby/block_api.rb +14 -0
- data/lib/voilkruby/broadcast_operations.json +497 -0
- data/lib/voilkruby/chain.rb +299 -0
- data/lib/voilkruby/chain_config.rb +22 -0
- data/lib/voilkruby/chain_stats_api.rb +15 -0
- data/lib/voilkruby/condenser_api.rb +99 -0
- data/lib/voilkruby/database_api.rb +5 -0
- data/lib/voilkruby/error_parser.rb +228 -0
- data/lib/voilkruby/follow_api.rb +7 -0
- data/lib/voilkruby/logger.rb +20 -0
- data/lib/voilkruby/market_history_api.rb +19 -0
- data/lib/voilkruby/methods.json +495 -0
- data/lib/voilkruby/mixins/acts_as_poster.rb +124 -0
- data/lib/voilkruby/mixins/acts_as_voter.rb +50 -0
- data/lib/voilkruby/mixins/acts_as_wallet.rb +67 -0
- data/lib/voilkruby/network_broadcast_api.rb +7 -0
- data/lib/voilkruby/operation.rb +101 -0
- data/lib/voilkruby/operation_ids.rb +98 -0
- data/lib/voilkruby/operation_types.rb +139 -0
- data/lib/voilkruby/stream.rb +527 -0
- data/lib/voilkruby/tag_api.rb +33 -0
- data/lib/voilkruby/transaction.rb +306 -0
- data/lib/voilkruby/type/amount.rb +57 -0
- data/lib/voilkruby/type/array.rb +17 -0
- data/lib/voilkruby/type/beneficiaries.rb +29 -0
- data/lib/voilkruby/type/future.rb +18 -0
- data/lib/voilkruby/type/hash.rb +17 -0
- data/lib/voilkruby/type/permission.rb +19 -0
- data/lib/voilkruby/type/point_in_time.rb +19 -0
- data/lib/voilkruby/type/price.rb +25 -0
- data/lib/voilkruby/type/public_key.rb +18 -0
- data/lib/voilkruby/type/serializer.rb +12 -0
- data/lib/voilkruby/type/u_int16.rb +19 -0
- data/lib/voilkruby/type/u_int32.rb +19 -0
- data/lib/voilkruby/utils.rb +170 -0
- data/lib/voilkruby/version.rb +4 -0
- data/voilkruby.gemspec +40 -0
- metadata +412 -0
@@ -0,0 +1,299 @@
|
|
1
|
+
module VoilkRuby
|
2
|
+
# Examples ...
|
3
|
+
#
|
4
|
+
# To vote on a post/comment:
|
5
|
+
#
|
6
|
+
# voilk = VoilkRuby::Chain.new(chain: :voilk, account_name: 'your account name', wif: 'your wif')
|
7
|
+
# voilk.vote!(10000, 'author', 'post-or-comment-permlink')
|
8
|
+
#
|
9
|
+
# To post and vote in the same transaction:
|
10
|
+
#
|
11
|
+
# voilk = VoilkRuby::Chain.new(chain: :voilk, account_name: 'your account name', wif: 'your wif')
|
12
|
+
# voilk.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
|
+
# voilk = VoilkRuby::Chain.new(chain: :voilk, 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_voilk_dollars: 0
|
24
|
+
# }
|
25
|
+
#
|
26
|
+
# voilk.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
|
+
# voilk = VoilkRuby::Chain.new(chain: :voilk)
|
72
|
+
# block = voilk.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
|
+
# voilk = VoilkRuby::Chain.new(chain: :voilk)
|
86
|
+
# ned = voilk.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
|
+
# voilk = VoilkRuby::Chain.new(chain: :voilk)
|
104
|
+
# comment = voilk.find_comment('inertia', 'kinda-spooky') # by account, permlink
|
105
|
+
# active_votes = comment.active_votes
|
106
|
+
#
|
107
|
+
# ... or ...
|
108
|
+
#
|
109
|
+
# comment = voilk.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. VOILK) price in the coin asset (e.g.
|
145
|
+
# COINS).
|
146
|
+
#
|
147
|
+
def base_per_mcoin
|
148
|
+
total_coining_fund_voilk = properties.total_coining_fund_voilk.to_f
|
149
|
+
total_coining_shares_mcoin = properties.total_coining_shares.to_f / 1e6
|
150
|
+
|
151
|
+
total_coining_fund_voilk / total_coining_shares_mcoin
|
152
|
+
end
|
153
|
+
|
154
|
+
# Returns the current base (e.g. VOILK) price in the debt asset (e.g VSD).
|
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 = VoilkRuby::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 :voilk then ChainConfig::NETWORKS_VOILK_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 VoilkRuby
|
2
|
+
module ChainConfig
|
3
|
+
EXPIRE_IN_SECS = 600
|
4
|
+
EXPIRE_IN_SECS_PROPOSAL = 24 * 60 * 60
|
5
|
+
|
6
|
+
NETWORKS_VOILK_CHAIN_ID = 'b510834141c312c2aa8837040734605f2333f1ecc4f634576372f9c12dc7e8b2'
|
7
|
+
NETWORKS_VOILK_ADDRESS_PREFIX = 'SHR'
|
8
|
+
NETWORKS_VOILK_CORE_ASSET = 'VOILK'
|
9
|
+
NETWORKS_VOILK_DEBT_ASSET = 'VSD'
|
10
|
+
NETWORKS_VOILK_COIN_ASSET = 'COINS'
|
11
|
+
NETWORKS_VOILK_DEFAULT_NODE = 'https://api.voilk.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.voilk.ws'
|
19
|
+
|
20
|
+
NETWORK_CHAIN_IDS = [NETWORKS_VOILK_CHAIN_ID, NETWORKS_TEST_CHAIN_ID]
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module VoilkRuby
|
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 VoilkRuby
|
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
|