beowulf-ruby-testnet 0.0.1
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/.gitignore +58 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +204 -0
- data/Rakefile +52 -0
- data/beowulf.gemspec +43 -0
- data/lib/beowulf.rb +35 -0
- data/lib/beowulf/account_history_api.rb +15 -0
- data/lib/beowulf/api.rb +845 -0
- data/lib/beowulf/base_error.rb +21 -0
- data/lib/beowulf/block_api.rb +14 -0
- data/lib/beowulf/broadcast_operations.json +74 -0
- data/lib/beowulf/chain_config.rb +15 -0
- data/lib/beowulf/condenser_api.rb +48 -0
- data/lib/beowulf/database_api.rb +5 -0
- data/lib/beowulf/error_parser.rb +228 -0
- data/lib/beowulf/logger.rb +20 -0
- data/lib/beowulf/methods.json +129 -0
- data/lib/beowulf/network_broadcast_api.rb +7 -0
- data/lib/beowulf/operation.rb +99 -0
- data/lib/beowulf/operation_ids.rb +33 -0
- data/lib/beowulf/operation_types.rb +31 -0
- data/lib/beowulf/transaction.rb +320 -0
- data/lib/beowulf/type/amount.rb +23 -0
- data/lib/beowulf/type/array.rb +17 -0
- data/lib/beowulf/type/authority.rb +48 -0
- data/lib/beowulf/type/authority_update.rb +50 -0
- data/lib/beowulf/type/future.rb +16 -0
- data/lib/beowulf/type/hash.rb +17 -0
- data/lib/beowulf/type/permission.rb +17 -0
- data/lib/beowulf/type/point_in_time.rb +17 -0
- data/lib/beowulf/type/public_key.rb +18 -0
- data/lib/beowulf/type/serializer.rb +12 -0
- data/lib/beowulf/type/u_int16.rb +17 -0
- data/lib/beowulf/type/u_int32.rb +17 -0
- data/lib/beowulf/utils.rb +221 -0
- data/lib/beowulf/version.rb +4 -0
- data/lib/beowulf/wallet.rb +212 -0
- metadata +463 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
module Beowulf
|
2
|
+
class BaseError < StandardError
|
3
|
+
def initialize(error, cause = nil)
|
4
|
+
@error = error
|
5
|
+
@cause = cause
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_s
|
9
|
+
if !!@cause
|
10
|
+
JSON[error: @error, cause: @cause] rescue {error: @error, cause: @cause}.to_s
|
11
|
+
else
|
12
|
+
JSON[@error] rescue @error
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module Beowulf; class ApiError < BaseError; end; end
|
19
|
+
module Beowulf; class TypeError < BaseError; end; end
|
20
|
+
module Beowulf; class OperationError < BaseError; end; end
|
21
|
+
module Beowulf; class TransactionError < BaseError; end; end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
[
|
2
|
+
{
|
3
|
+
"roles": ["owner"],
|
4
|
+
"operation": "transfer",
|
5
|
+
"params": [
|
6
|
+
"from",
|
7
|
+
"to",
|
8
|
+
"amount",
|
9
|
+
"fee",
|
10
|
+
"memo"
|
11
|
+
]
|
12
|
+
},
|
13
|
+
{
|
14
|
+
"roles": ["owner"],
|
15
|
+
"operation": "transfer_to_vesting",
|
16
|
+
"params": [
|
17
|
+
"from",
|
18
|
+
"to",
|
19
|
+
"amount",
|
20
|
+
"fee"
|
21
|
+
]
|
22
|
+
},
|
23
|
+
{
|
24
|
+
"roles": ["owner"],
|
25
|
+
"operation": "withdraw_vesting",
|
26
|
+
"params": [
|
27
|
+
"account",
|
28
|
+
"vesting_shares",
|
29
|
+
"fee"
|
30
|
+
]
|
31
|
+
},
|
32
|
+
{
|
33
|
+
"roles": ["owner"],
|
34
|
+
"operation": "account_create",
|
35
|
+
"params": [
|
36
|
+
"fee",
|
37
|
+
"creator",
|
38
|
+
"new_account_name",
|
39
|
+
"owner",
|
40
|
+
"json_metadata"
|
41
|
+
]
|
42
|
+
},
|
43
|
+
{
|
44
|
+
"roles": ["owner"],
|
45
|
+
"operation": "account_update",
|
46
|
+
"params": [
|
47
|
+
"account",
|
48
|
+
"owner",
|
49
|
+
"json_metadata",
|
50
|
+
"fee"
|
51
|
+
]
|
52
|
+
},
|
53
|
+
{
|
54
|
+
"roles": ["owner"],
|
55
|
+
"operation": "supernode_update",
|
56
|
+
"params": [
|
57
|
+
"owner",
|
58
|
+
"url",
|
59
|
+
"block_signing_key",
|
60
|
+
"props",
|
61
|
+
"fee"
|
62
|
+
]
|
63
|
+
},
|
64
|
+
{
|
65
|
+
"roles": ["owner"],
|
66
|
+
"operation": "account_supernode_vote",
|
67
|
+
"params": [
|
68
|
+
"account",
|
69
|
+
"supernode",
|
70
|
+
"approve",
|
71
|
+
"fee"
|
72
|
+
]
|
73
|
+
}
|
74
|
+
]
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Beowulf
|
2
|
+
module ChainConfig
|
3
|
+
EXPIRE_IN_SECS = 600
|
4
|
+
EXPIRE_IN_SECS_PROPOSAL = 24 * 60 * 60
|
5
|
+
|
6
|
+
NETWORKS_BEOWULF_CHAIN_ID = '430b37f23cf146d42f15376f341d7f8f5a1ad6f4e63affdeb5dc61d55d8c95a7'
|
7
|
+
NETWORKS_BEOWULF_ADDRESS_PREFIX = 'BEO'
|
8
|
+
NETWORKS_BEOWULF_BWF_ASSET = 'BWF'
|
9
|
+
NETWORKS_BEOWULF_W_ASSET = 'W'
|
10
|
+
NETWORKS_BEOWULF_VEST_ASSET = 'M'
|
11
|
+
NETWORKS_BEOWULF_DEFAULT_NODE = 'https://testnet-bw.beowulfchain.com/rpc'
|
12
|
+
|
13
|
+
NETWORK_CHAIN_IDS = [NETWORKS_BEOWULF_CHAIN_ID]
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Beowulf
|
2
|
+
class CondenserApi < Api
|
3
|
+
METHOD_NAMES = [
|
4
|
+
# :broadcast_block,
|
5
|
+
:broadcast_transaction,
|
6
|
+
:broadcast_transaction_synchronous,
|
7
|
+
:get_account_count,
|
8
|
+
:get_accounts,
|
9
|
+
:get_balance,
|
10
|
+
:get_active_supernodes,
|
11
|
+
:get_block,
|
12
|
+
:get_block_header,
|
13
|
+
:get_config,
|
14
|
+
:get_dynamic_global_properties,
|
15
|
+
:get_hardfork_version,
|
16
|
+
:get_key_references,
|
17
|
+
:get_next_scheduled_hardfork,
|
18
|
+
:get_owner_history,
|
19
|
+
:get_potential_signatures,
|
20
|
+
:get_required_signatures,
|
21
|
+
:get_transaction,
|
22
|
+
:get_transaction_hex,
|
23
|
+
:get_transaction_with_status,
|
24
|
+
:get_version,
|
25
|
+
:get_supernode_by_account,
|
26
|
+
:get_supernode_count,
|
27
|
+
:get_supernode_schedule,
|
28
|
+
:get_supernodes,
|
29
|
+
:get_supernodes_by_vote,
|
30
|
+
:lookup_account_names,
|
31
|
+
:lookup_accounts,
|
32
|
+
:lookup_supernode_accounts,
|
33
|
+
:list_smt_tokens,
|
34
|
+
:verify_authority,
|
35
|
+
:find_smt_tokens_by_name,
|
36
|
+
:get_supernode_voted_by_acc,
|
37
|
+
:get_pending_transaction_count
|
38
|
+
].freeze
|
39
|
+
|
40
|
+
def method_names
|
41
|
+
METHOD_NAMES
|
42
|
+
end
|
43
|
+
|
44
|
+
def api_name
|
45
|
+
:condenser_api
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,228 @@
|
|
1
|
+
module Beowulf
|
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
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Beowulf
|
4
|
+
# The logger that Beowulf uses for reporting errors.
|
5
|
+
#
|
6
|
+
# @return [Logger]
|
7
|
+
def self.logger
|
8
|
+
@@logger ||= Logger.new(STDOUT).tap do |logger|
|
9
|
+
logger.progname = 'beowulf'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Sets the logger that Beowulf uses for reporting errors.
|
14
|
+
#
|
15
|
+
# @param logger [Logger] The logger to set as Beowulf's logger.
|
16
|
+
# @return [void]
|
17
|
+
def self.logger=(logger)
|
18
|
+
@@logger = logger
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
[
|
2
|
+
{
|
3
|
+
"api": "database_api",
|
4
|
+
"method": "get_block_header",
|
5
|
+
"params": ["blockNum"]
|
6
|
+
},
|
7
|
+
{
|
8
|
+
"api": "database_api",
|
9
|
+
"method": "get_block",
|
10
|
+
"params": ["blockNum"]
|
11
|
+
},
|
12
|
+
{
|
13
|
+
"api": "database_api",
|
14
|
+
"method": "get_config"
|
15
|
+
},
|
16
|
+
{
|
17
|
+
"api": "database_api",
|
18
|
+
"method": "get_dynamic_global_properties"
|
19
|
+
},
|
20
|
+
{
|
21
|
+
"api": "database_api",
|
22
|
+
"method": "get_supernode_schedule"
|
23
|
+
},
|
24
|
+
{
|
25
|
+
"api": "database_api",
|
26
|
+
"method": "get_hardfork_version"
|
27
|
+
},
|
28
|
+
{
|
29
|
+
"api": "database_api",
|
30
|
+
"method": "get_next_scheduled_hardfork"
|
31
|
+
},
|
32
|
+
{
|
33
|
+
"api": "account_by_key_api",
|
34
|
+
"method": "get_key_references",
|
35
|
+
"params": ["key"]
|
36
|
+
},
|
37
|
+
{
|
38
|
+
"api": "database_api",
|
39
|
+
"method": "get_accounts",
|
40
|
+
"params": ["names"]
|
41
|
+
},
|
42
|
+
{
|
43
|
+
"api": "database_api",
|
44
|
+
"method": "lookup_account_names",
|
45
|
+
"params": ["accountNames"]
|
46
|
+
},
|
47
|
+
{
|
48
|
+
"api": "database_api",
|
49
|
+
"method": "lookup_accounts",
|
50
|
+
"params": ["lowerBoundName", "limit"]
|
51
|
+
},
|
52
|
+
{
|
53
|
+
"api": "database_api",
|
54
|
+
"method": "get_account_count"
|
55
|
+
},
|
56
|
+
{
|
57
|
+
"api": "database_api",
|
58
|
+
"method": "get_owner_history",
|
59
|
+
"params": ["account"]
|
60
|
+
},
|
61
|
+
{
|
62
|
+
"api": "database_api",
|
63
|
+
"method": "get_transaction_hex",
|
64
|
+
"params": ["trx"]
|
65
|
+
},
|
66
|
+
{
|
67
|
+
"api": "database_api",
|
68
|
+
"method": "get_transaction",
|
69
|
+
"params": ["trxId"]
|
70
|
+
},
|
71
|
+
{
|
72
|
+
"api": "database_api",
|
73
|
+
"method": "get_required_signatures",
|
74
|
+
"params": ["trx", "availableKeys"]
|
75
|
+
},
|
76
|
+
{
|
77
|
+
"api": "database_api",
|
78
|
+
"method": "get_potential_signatures",
|
79
|
+
"params": ["trx"]
|
80
|
+
},
|
81
|
+
{
|
82
|
+
"api": "database_api",
|
83
|
+
"method": "verify_authority",
|
84
|
+
"params": ["trx"]
|
85
|
+
},
|
86
|
+
{
|
87
|
+
"api": "database_api",
|
88
|
+
"method": "get_supernodes",
|
89
|
+
"params": ["supernodeIds"]
|
90
|
+
},
|
91
|
+
{
|
92
|
+
"api": "database_api",
|
93
|
+
"method": "get_supernode_by_account",
|
94
|
+
"params": ["accountName"]
|
95
|
+
},
|
96
|
+
{
|
97
|
+
"api": "database_api",
|
98
|
+
"method": "get_supernodes_by_vote",
|
99
|
+
"params": ["from", "limit"]
|
100
|
+
},
|
101
|
+
{
|
102
|
+
"api": "database_api",
|
103
|
+
"method": "lookup_supernode_accounts",
|
104
|
+
"params": ["lowerBoundName", "limit"]
|
105
|
+
},
|
106
|
+
{
|
107
|
+
"api": "database_api",
|
108
|
+
"method": "get_supernode_count"
|
109
|
+
},
|
110
|
+
{
|
111
|
+
"api": "database_api",
|
112
|
+
"method": "get_active_supernodes"
|
113
|
+
},
|
114
|
+
{
|
115
|
+
"api": "network_broadcast_api",
|
116
|
+
"method": "broadcast_transaction",
|
117
|
+
"params": ["trx"]
|
118
|
+
},
|
119
|
+
{
|
120
|
+
"api": "network_broadcast_api",
|
121
|
+
"method": "broadcast_transaction_synchronous",
|
122
|
+
"params": ["trx"]
|
123
|
+
},
|
124
|
+
{
|
125
|
+
"api": "network_broadcast_api",
|
126
|
+
"method": "broadcast_block",
|
127
|
+
"params": ["b"]
|
128
|
+
}
|
129
|
+
]
|