erpc-sdk 0.9.1 → 0.11.0
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 +4 -4
- data/LICENSES/Apache-2.0.txt +201 -0
- data/LICENSES/BSD-3-Clause-Google-Protobuf.txt +29 -0
- data/README.md +46 -9
- data/THIRD_PARTY_NOTICES.md +41 -0
- data/lib/erpc_sdk/arbitrum.rb +62 -0
- data/lib/erpc_sdk/arc.rb +33 -0
- data/lib/erpc_sdk/base.rb +333 -0
- data/lib/erpc_sdk/bitcoin.rb +126 -0
- data/lib/erpc_sdk/bitcoin_codec.rb +574 -0
- data/lib/erpc_sdk/bitcoin_transport.rb +369 -0
- data/lib/erpc_sdk/bsc.rb +33 -0
- data/lib/erpc_sdk/client.rb +109 -1
- data/lib/erpc_sdk/config.rb +168 -5
- data/lib/erpc_sdk/errors.rb +31 -1
- data/lib/erpc_sdk/generated/rpc_network_contract.rb +3196 -0
- data/lib/erpc_sdk/hyperliquid.rb +33 -0
- data/lib/erpc_sdk/kaia.rb +33 -0
- data/lib/erpc_sdk/monad.rb +33 -0
- data/lib/erpc_sdk/near.rb +276 -0
- data/lib/erpc_sdk/near_transport.rb +345 -0
- data/lib/erpc_sdk/optimism.rb +33 -0
- data/lib/erpc_sdk/polygon.rb +33 -0
- data/lib/erpc_sdk/robinhood.rb +33 -0
- data/lib/erpc_sdk/soneium.rb +33 -0
- data/lib/erpc_sdk/sui.rb +5 -0
- data/lib/erpc_sdk/sui_grpc_web.rb +226 -0
- data/lib/erpc_sdk/sui_proto.rb +370 -0
- data/lib/erpc_sdk/transport.rb +28 -0
- data/lib/erpc_sdk/tron.rb +33 -0
- data/lib/erpc_sdk/version.rb +1 -1
- data/lib/erpc_sdk.rb +21 -0
- metadata +43 -7
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bigdecimal"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
require_relative "errors"
|
|
7
|
+
|
|
8
|
+
module ERPC
|
|
9
|
+
# The resource and numeric limits are intentionally private to the new
|
|
10
|
+
# Bitcoin decoder. Existing JSON-RPC namespaces continue to use their
|
|
11
|
+
# released parser and limits.
|
|
12
|
+
BitcoinResponseLimits = Struct.new(
|
|
13
|
+
:decoded_entity_bytes,
|
|
14
|
+
:container_depth,
|
|
15
|
+
:json_tokens,
|
|
16
|
+
:numeric_lexeme_bytes,
|
|
17
|
+
:coefficient_digits,
|
|
18
|
+
:explicit_exponent_min,
|
|
19
|
+
:explicit_exponent_max,
|
|
20
|
+
:canonical_exponent_min,
|
|
21
|
+
:canonical_exponent_max,
|
|
22
|
+
keyword_init: true
|
|
23
|
+
) do
|
|
24
|
+
def initialize(**values)
|
|
25
|
+
super
|
|
26
|
+
each_pair { |key, value| self[key] = Integer(value) }
|
|
27
|
+
freeze
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
BITCOIN_RESPONSE_LIMITS = BitcoinResponseLimits.new(
|
|
32
|
+
decoded_entity_bytes: 67_108_864,
|
|
33
|
+
container_depth: 64,
|
|
34
|
+
json_tokens: 1_000_000,
|
|
35
|
+
numeric_lexeme_bytes: 1_024,
|
|
36
|
+
coefficient_digits: 1_000,
|
|
37
|
+
explicit_exponent_min: -10_000,
|
|
38
|
+
explicit_exponent_max: 10_000,
|
|
39
|
+
canonical_exponent_min: -10_000,
|
|
40
|
+
canonical_exponent_max: 10_000
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
BitcoinNumberTuple = Struct.new(:sign, :coefficient, :exponent10, keyword_init: true) do
|
|
44
|
+
def initialize(sign:, coefficient:, exponent10:)
|
|
45
|
+
super
|
|
46
|
+
freeze
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Scanner metadata is deliberately small: the parsed JSON object is still
|
|
51
|
+
# constructed by Ruby's JSON parser, while this metadata lets the typed
|
|
52
|
+
# block-count projection retain the selected original result token.
|
|
53
|
+
BitcoinPreflightResult = Struct.new(
|
|
54
|
+
:token_count,
|
|
55
|
+
:numeric_token_count,
|
|
56
|
+
:max_depth,
|
|
57
|
+
:number_tokens,
|
|
58
|
+
:root_result_token,
|
|
59
|
+
keyword_init: true
|
|
60
|
+
) do
|
|
61
|
+
def initialize(**values)
|
|
62
|
+
values[:number_tokens] = values.fetch(:number_tokens).freeze
|
|
63
|
+
super
|
|
64
|
+
freeze
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
BitcoinNumberToken = Struct.new(:raw, :integer, :tuple, keyword_init: true) do
|
|
69
|
+
def initialize(raw:, integer:, tuple:)
|
|
70
|
+
super
|
|
71
|
+
freeze
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
module BitcoinCodec
|
|
76
|
+
module_function
|
|
77
|
+
|
|
78
|
+
INTEGER_TOKEN = /\A-?(?:0|[1-9][0-9]*)\z/.freeze
|
|
79
|
+
UNSIGNED_INTEGER_TOKEN = /\A(?:0|[1-9][0-9]*)\z/.freeze
|
|
80
|
+
NUMBER_TOKEN = /\A(-?)(0|[1-9][0-9]*)(?:\.([0-9]+))?(?:[eE]([+-]?[0-9]+))?\z/.freeze
|
|
81
|
+
TOKEN_DELIMITERS = " \t\r\n,:[}]".freeze
|
|
82
|
+
|
|
83
|
+
# Canonicalize one original JSON number lexeme without converting it to a
|
|
84
|
+
# floating point value or expanding an exponent.
|
|
85
|
+
def canonicalize_number(raw, limits: BITCOIN_RESPONSE_LIMITS)
|
|
86
|
+
unless raw.is_a?(String) && raw.ascii_only? && raw.bytesize <= limits.numeric_lexeme_bytes
|
|
87
|
+
invalid_response!
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
match = NUMBER_TOKEN.match(raw)
|
|
91
|
+
invalid_response! unless match
|
|
92
|
+
|
|
93
|
+
sign_token, integer_part, fraction_part, exponent_part = match.captures
|
|
94
|
+
fraction_part ||= ""
|
|
95
|
+
explicit_exponent = checked_exponent(exponent_part || "0", limits: limits)
|
|
96
|
+
raw_coefficient = integer_part + fraction_part
|
|
97
|
+
invalid_response! if raw_coefficient.length > limits.coefficient_digits
|
|
98
|
+
|
|
99
|
+
without_leading_zeroes = raw_coefficient.sub(/\A0+/, "")
|
|
100
|
+
return BitcoinNumberTuple.new(sign: 0, coefficient: "0", exponent10: "0") if without_leading_zeroes.empty?
|
|
101
|
+
|
|
102
|
+
coefficient = without_leading_zeroes.sub(/0+\z/, "")
|
|
103
|
+
trailing_zeroes = without_leading_zeroes.length - coefficient.length
|
|
104
|
+
exponent10 = explicit_exponent - fraction_part.length + trailing_zeroes
|
|
105
|
+
unless exponent10.between?(limits.canonical_exponent_min, limits.canonical_exponent_max)
|
|
106
|
+
invalid_response!
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
BitcoinNumberTuple.new(
|
|
110
|
+
sign: sign_token == "-" ? -1 : 1,
|
|
111
|
+
coefficient: coefficient,
|
|
112
|
+
exponent10: exponent10.to_s
|
|
113
|
+
)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
alias canonicalize_bitcoin_number canonicalize_number
|
|
117
|
+
module_function :canonicalize_bitcoin_number
|
|
118
|
+
|
|
119
|
+
def preflight(text, limits: BITCOIN_RESPONSE_LIMITS)
|
|
120
|
+
invalid_response! unless text.is_a?(String)
|
|
121
|
+
invalid_response! unless text.encoding == Encoding::UTF_8 && text.valid_encoding?
|
|
122
|
+
|
|
123
|
+
scanner = Scanner.new(text, limits)
|
|
124
|
+
scanner.run
|
|
125
|
+
rescue InvalidResponseError
|
|
126
|
+
raise
|
|
127
|
+
rescue StandardError
|
|
128
|
+
invalid_response!
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
alias preflight_bitcoin_json preflight
|
|
132
|
+
module_function :preflight_bitcoin_json
|
|
133
|
+
|
|
134
|
+
def decode(text, expected_id, redactions: [], projector: nil, limits: BITCOIN_RESPONSE_LIMITS)
|
|
135
|
+
metadata = preflight(text, limits: limits)
|
|
136
|
+
response = parse_document(text, metadata, limits: limits)
|
|
137
|
+
unless response.is_a?(Hash)
|
|
138
|
+
invalid_response!
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
actual_id = response["id"]
|
|
142
|
+
unless valid_response_id?(actual_id) && actual_id == expected_id
|
|
143
|
+
invalid_response!
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
if response.key?("error") && !response["error"].nil?
|
|
147
|
+
raise_rpc_error(response["error"], redactions)
|
|
148
|
+
end
|
|
149
|
+
invalid_response! unless response.key?("result")
|
|
150
|
+
|
|
151
|
+
if projector
|
|
152
|
+
begin
|
|
153
|
+
return projector.call(response["result"], metadata.root_result_token)
|
|
154
|
+
rescue InvalidResponseError
|
|
155
|
+
raise
|
|
156
|
+
rescue StandardError
|
|
157
|
+
invalid_response!
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
response["result"]
|
|
162
|
+
rescue InvalidResponseError
|
|
163
|
+
raise
|
|
164
|
+
rescue JSON::ParserError, JSON::NestingError, ArgumentError, EncodingError, SystemStackError
|
|
165
|
+
invalid_response!
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
alias unwrap_bitcoin_rpc_response decode
|
|
169
|
+
module_function :unwrap_bitcoin_rpc_response
|
|
170
|
+
|
|
171
|
+
def as_string(value, _token = nil)
|
|
172
|
+
invalid_response! unless value.is_a?(String)
|
|
173
|
+
|
|
174
|
+
value
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
alias as_bitcoin_string as_string
|
|
178
|
+
module_function :as_bitcoin_string
|
|
179
|
+
|
|
180
|
+
def as_block_count(value, token = nil)
|
|
181
|
+
raw = token.is_a?(BitcoinNumberToken) ? token.raw : nil
|
|
182
|
+
invalid_response! unless raw&.match?(UNSIGNED_INTEGER_TOKEN)
|
|
183
|
+
invalid_response! unless value.is_a?(Integer) && !value.is_a?(TrueClass) && value >= 0
|
|
184
|
+
invalid_response! if value > 18_446_744_073_709_551_615
|
|
185
|
+
value
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
alias as_bitcoin_block_count as_block_count
|
|
189
|
+
module_function :as_bitcoin_block_count
|
|
190
|
+
|
|
191
|
+
def as_nullable_object(value, _token = nil)
|
|
192
|
+
return nil if value.nil?
|
|
193
|
+
|
|
194
|
+
invalid_response! unless value.is_a?(Hash)
|
|
195
|
+
|
|
196
|
+
value
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
alias as_bitcoin_nullable_object as_nullable_object
|
|
200
|
+
module_function :as_bitcoin_nullable_object
|
|
201
|
+
|
|
202
|
+
def invalid_response!
|
|
203
|
+
raise InvalidResponseError.new("ERPC returned an invalid Bitcoin response"), cause: nil
|
|
204
|
+
end
|
|
205
|
+
private_class_method :invalid_response!
|
|
206
|
+
|
|
207
|
+
def checked_exponent(raw, limits: BITCOIN_RESPONSE_LIMITS)
|
|
208
|
+
sign = raw.start_with?("-") ? -1 : 1
|
|
209
|
+
digits = raw.start_with?("+", "-") ? raw[1..] : raw
|
|
210
|
+
invalid_response! if digits.nil? || digits.empty?
|
|
211
|
+
|
|
212
|
+
normalized = digits.sub(/\A0+/, "")
|
|
213
|
+
normalized = "0" if normalized.empty?
|
|
214
|
+
value = 0
|
|
215
|
+
normalized.each_byte do |byte|
|
|
216
|
+
invalid_response! unless byte.between?(48, 57)
|
|
217
|
+
|
|
218
|
+
value = (value * 10) + byte - 48
|
|
219
|
+
invalid_response! if value > limits.explicit_exponent_max
|
|
220
|
+
end
|
|
221
|
+
result = sign * value
|
|
222
|
+
invalid_response! unless result.between?(limits.explicit_exponent_min, limits.explicit_exponent_max)
|
|
223
|
+
|
|
224
|
+
result
|
|
225
|
+
end
|
|
226
|
+
private_class_method :checked_exponent
|
|
227
|
+
|
|
228
|
+
def parse_document(text, metadata, limits: BITCOIN_RESPONSE_LIMITS)
|
|
229
|
+
decimal_tokens = metadata.number_tokens.reject(&:integer)
|
|
230
|
+
decimal_index = 0
|
|
231
|
+
adapter = Class.new do
|
|
232
|
+
define_singleton_method(:new) do |raw|
|
|
233
|
+
descriptor = decimal_tokens.fetch(decimal_index) do
|
|
234
|
+
BitcoinCodec.send(:invalid_response!)
|
|
235
|
+
end
|
|
236
|
+
decimal_index += 1
|
|
237
|
+
BitcoinCodec.send(:invalid_response!) unless raw == descriptor.raw
|
|
238
|
+
|
|
239
|
+
value = BigDecimal(raw)
|
|
240
|
+
tuple = BitcoinCodec.send(:big_decimal_tuple, value, limits: limits)
|
|
241
|
+
if !value.finite? || (descriptor.tuple.sign != 0 && value.zero?) || tuple != descriptor.tuple
|
|
242
|
+
BitcoinCodec.send(:invalid_response!)
|
|
243
|
+
end
|
|
244
|
+
value
|
|
245
|
+
rescue InvalidResponseError
|
|
246
|
+
raise
|
|
247
|
+
rescue StandardError
|
|
248
|
+
BitcoinCodec.send(:invalid_response!)
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
JSON.parse(
|
|
253
|
+
text,
|
|
254
|
+
create_additions: false,
|
|
255
|
+
allow_nan: false,
|
|
256
|
+
max_nesting: limits.container_depth,
|
|
257
|
+
decimal_class: adapter
|
|
258
|
+
)
|
|
259
|
+
rescue InvalidResponseError
|
|
260
|
+
raise
|
|
261
|
+
rescue JSON::ParserError, JSON::NestingError, ArgumentError, TypeError, SystemStackError
|
|
262
|
+
invalid_response!
|
|
263
|
+
end
|
|
264
|
+
private_class_method :parse_document
|
|
265
|
+
|
|
266
|
+
def big_decimal_tuple(value, limits: BITCOIN_RESPONSE_LIMITS)
|
|
267
|
+
invalid_response! unless value.is_a?(BigDecimal) && value.finite?
|
|
268
|
+
return BitcoinNumberTuple.new(sign: 0, coefficient: "0", exponent10: "0") if value.zero?
|
|
269
|
+
|
|
270
|
+
sign, digits, _base, exponent = value.split
|
|
271
|
+
coefficient = digits.sub(/\A0+/, "")
|
|
272
|
+
invalid_response! if coefficient.empty?
|
|
273
|
+
coefficient = coefficient.sub(/0+\z/, "")
|
|
274
|
+
trailing_zeroes = digits.length - coefficient.length
|
|
275
|
+
exponent10 = exponent - digits.length + trailing_zeroes
|
|
276
|
+
invalid_response! unless exponent10.between?(limits.canonical_exponent_min, limits.canonical_exponent_max)
|
|
277
|
+
|
|
278
|
+
BitcoinNumberTuple.new(
|
|
279
|
+
sign: sign.negative? ? -1 : 1,
|
|
280
|
+
coefficient: coefficient,
|
|
281
|
+
exponent10: exponent10.to_s
|
|
282
|
+
)
|
|
283
|
+
end
|
|
284
|
+
private_class_method :big_decimal_tuple
|
|
285
|
+
|
|
286
|
+
def valid_response_id?(value)
|
|
287
|
+
(value.is_a?(Integer) && !value.is_a?(TrueClass) && !value.is_a?(FalseClass)) || value.is_a?(String)
|
|
288
|
+
end
|
|
289
|
+
private_class_method :valid_response_id?
|
|
290
|
+
|
|
291
|
+
def raise_rpc_error(error, redactions)
|
|
292
|
+
unless error.is_a?(Hash) && error["code"].is_a?(Integer) &&
|
|
293
|
+
!error["code"].is_a?(TrueClass) && !error["code"].is_a?(FalseClass) &&
|
|
294
|
+
error["message"].is_a?(String)
|
|
295
|
+
invalid_response!
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
credentials = redactions.is_a?(Array) ? redactions : [redactions]
|
|
299
|
+
data = error.key?("data") ? Redaction.value(error["data"], credentials) : nil
|
|
300
|
+
message = Redaction.text(error["message"], credentials)
|
|
301
|
+
raise JsonRpcError.new(error["code"], message, data), cause: nil
|
|
302
|
+
end
|
|
303
|
+
private_class_method :raise_rpc_error
|
|
304
|
+
|
|
305
|
+
class Scanner
|
|
306
|
+
def initialize(text, limits)
|
|
307
|
+
@text = text
|
|
308
|
+
@limits = limits
|
|
309
|
+
@index = 0
|
|
310
|
+
@token_count = 0
|
|
311
|
+
@numeric_token_count = 0
|
|
312
|
+
@max_depth = 0
|
|
313
|
+
@number_tokens = []
|
|
314
|
+
@stack = []
|
|
315
|
+
@root_result_token = nil
|
|
316
|
+
@root_result_seen = false
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def run
|
|
320
|
+
skip_whitespace
|
|
321
|
+
parse_value(root: true)
|
|
322
|
+
skip_whitespace
|
|
323
|
+
invalid! unless @index == @text.length
|
|
324
|
+
invalid! unless @stack.empty?
|
|
325
|
+
|
|
326
|
+
BitcoinPreflightResult.new(
|
|
327
|
+
token_count: @token_count,
|
|
328
|
+
numeric_token_count: @numeric_token_count,
|
|
329
|
+
max_depth: @max_depth,
|
|
330
|
+
number_tokens: @number_tokens,
|
|
331
|
+
root_result_token: @root_result_seen ? @root_result_token : nil
|
|
332
|
+
)
|
|
333
|
+
rescue InvalidResponseError
|
|
334
|
+
raise
|
|
335
|
+
rescue StandardError
|
|
336
|
+
invalid!
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
private
|
|
340
|
+
|
|
341
|
+
def parse_value(root: false, object_key: nil)
|
|
342
|
+
skip_whitespace
|
|
343
|
+
invalid! if @index >= @text.length
|
|
344
|
+
value_start = @index
|
|
345
|
+
character = @text[@index]
|
|
346
|
+
|
|
347
|
+
case character
|
|
348
|
+
when '"'
|
|
349
|
+
add_token
|
|
350
|
+
scan_string
|
|
351
|
+
mark_root_result(nil, object_key) if root || object_key == "result"
|
|
352
|
+
when "{"
|
|
353
|
+
add_token
|
|
354
|
+
parse_object
|
|
355
|
+
mark_root_result(nil, object_key) if root || object_key == "result"
|
|
356
|
+
when "["
|
|
357
|
+
add_token
|
|
358
|
+
parse_array
|
|
359
|
+
mark_root_result(nil, object_key) if root || object_key == "result"
|
|
360
|
+
when "-", /[0-9]/
|
|
361
|
+
raw = scan_number
|
|
362
|
+
tuple = BitcoinCodec.canonicalize_number(raw, limits: @limits)
|
|
363
|
+
token = BitcoinNumberToken.new(
|
|
364
|
+
raw: raw,
|
|
365
|
+
integer: !raw.include?(".") && !raw.match?(/[eE]/),
|
|
366
|
+
tuple: tuple
|
|
367
|
+
)
|
|
368
|
+
@number_tokens << token
|
|
369
|
+
@numeric_token_count += 1
|
|
370
|
+
add_token
|
|
371
|
+
mark_root_result(token, object_key) if root || object_key == "result"
|
|
372
|
+
when "t"
|
|
373
|
+
scan_literal("true")
|
|
374
|
+
add_token
|
|
375
|
+
mark_root_result(nil, object_key) if root || object_key == "result"
|
|
376
|
+
when "f"
|
|
377
|
+
scan_literal("false")
|
|
378
|
+
add_token
|
|
379
|
+
mark_root_result(nil, object_key) if root || object_key == "result"
|
|
380
|
+
when "n"
|
|
381
|
+
scan_literal("null")
|
|
382
|
+
add_token
|
|
383
|
+
mark_root_result(nil, object_key) if root || object_key == "result"
|
|
384
|
+
else
|
|
385
|
+
invalid!
|
|
386
|
+
end
|
|
387
|
+
value_start
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def parse_object
|
|
391
|
+
push_container("{")
|
|
392
|
+
@index += 1
|
|
393
|
+
skip_whitespace
|
|
394
|
+
if current_character == "}"
|
|
395
|
+
@index += 1
|
|
396
|
+
pop_container("{")
|
|
397
|
+
return
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
loop do
|
|
401
|
+
invalid! unless current_character == '"'
|
|
402
|
+
add_token
|
|
403
|
+
key_start = @index
|
|
404
|
+
scan_string
|
|
405
|
+
key = decode_key(@text[key_start...@index])
|
|
406
|
+
skip_whitespace
|
|
407
|
+
invalid! unless current_character == ":"
|
|
408
|
+
@index += 1
|
|
409
|
+
parse_value(object_key: key)
|
|
410
|
+
skip_whitespace
|
|
411
|
+
case current_character
|
|
412
|
+
when ","
|
|
413
|
+
@index += 1
|
|
414
|
+
skip_whitespace
|
|
415
|
+
when "}"
|
|
416
|
+
@index += 1
|
|
417
|
+
pop_container("{")
|
|
418
|
+
break
|
|
419
|
+
else
|
|
420
|
+
invalid!
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
def parse_array
|
|
426
|
+
push_container("[")
|
|
427
|
+
@index += 1
|
|
428
|
+
skip_whitespace
|
|
429
|
+
if current_character == "]"
|
|
430
|
+
@index += 1
|
|
431
|
+
pop_container("[")
|
|
432
|
+
return
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
loop do
|
|
436
|
+
parse_value
|
|
437
|
+
skip_whitespace
|
|
438
|
+
case current_character
|
|
439
|
+
when ","
|
|
440
|
+
@index += 1
|
|
441
|
+
skip_whitespace
|
|
442
|
+
when "]"
|
|
443
|
+
@index += 1
|
|
444
|
+
pop_container("[")
|
|
445
|
+
break
|
|
446
|
+
else
|
|
447
|
+
invalid!
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
def push_container(type)
|
|
453
|
+
@stack << type
|
|
454
|
+
@max_depth = [@max_depth, @stack.length].max
|
|
455
|
+
invalid! if @stack.length > @limits.container_depth
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
def pop_container(type)
|
|
459
|
+
invalid! unless @stack.pop == type
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
def scan_string
|
|
463
|
+
invalid! unless current_character == '"'
|
|
464
|
+
@index += 1
|
|
465
|
+
while @index < @text.length
|
|
466
|
+
character = @text[@index]
|
|
467
|
+
if character == '"'
|
|
468
|
+
@index += 1
|
|
469
|
+
return
|
|
470
|
+
elsif character == "\\"
|
|
471
|
+
@index += 1
|
|
472
|
+
invalid! if @index >= @text.length
|
|
473
|
+
escape = @text[@index]
|
|
474
|
+
if escape == "u"
|
|
475
|
+
4.times do
|
|
476
|
+
@index += 1
|
|
477
|
+
invalid! if @index >= @text.length || !@text[@index].match?(/[0-9A-Fa-f]/)
|
|
478
|
+
end
|
|
479
|
+
elsif !%w[" \\ / b f n r t].include?(escape)
|
|
480
|
+
invalid!
|
|
481
|
+
end
|
|
482
|
+
elsif character.ord < 0x20
|
|
483
|
+
invalid!
|
|
484
|
+
end
|
|
485
|
+
@index += 1
|
|
486
|
+
end
|
|
487
|
+
invalid!
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
def decode_key(raw)
|
|
491
|
+
JSON.parse(raw, create_additions: false)
|
|
492
|
+
rescue JSON::ParserError, ArgumentError, TypeError
|
|
493
|
+
invalid!
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
def scan_number
|
|
497
|
+
start = @index
|
|
498
|
+
@index += 1 if current_character == "-"
|
|
499
|
+
invalid! if @index >= @text.length
|
|
500
|
+
if current_character == "0"
|
|
501
|
+
@index += 1
|
|
502
|
+
invalid! if digit?(current_character)
|
|
503
|
+
elsif current_character&.match?(/[1-9]/)
|
|
504
|
+
@index += 1 while digit?(current_character)
|
|
505
|
+
else
|
|
506
|
+
invalid!
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
if current_character == "."
|
|
510
|
+
@index += 1
|
|
511
|
+
fraction_start = @index
|
|
512
|
+
@index += 1 while digit?(current_character)
|
|
513
|
+
invalid! if @index == fraction_start
|
|
514
|
+
end
|
|
515
|
+
if %w[e E].include?(current_character)
|
|
516
|
+
@index += 1
|
|
517
|
+
@index += 1 if %w[+ -].include?(current_character)
|
|
518
|
+
exponent_start = @index
|
|
519
|
+
@index += 1 while digit?(current_character)
|
|
520
|
+
invalid! if @index == exponent_start
|
|
521
|
+
end
|
|
522
|
+
unless @index >= @text.length || TOKEN_DELIMITERS.include?(current_character)
|
|
523
|
+
invalid!
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
@text[start...@index]
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
def scan_literal(literal)
|
|
530
|
+
invalid! unless @text[@index, literal.length] == literal
|
|
531
|
+
@index += literal.length
|
|
532
|
+
unless @index >= @text.length || TOKEN_DELIMITERS.include?(current_character)
|
|
533
|
+
invalid!
|
|
534
|
+
end
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
def mark_root_result(token, object_key)
|
|
538
|
+
return unless @stack.empty? || (@stack.length == 1 && @stack.first == "{")
|
|
539
|
+
return unless object_key == "result"
|
|
540
|
+
|
|
541
|
+
@root_result_seen = true
|
|
542
|
+
@root_result_token = token
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
def add_token
|
|
546
|
+
@token_count += 1
|
|
547
|
+
invalid! if @token_count > @limits.json_tokens
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
def skip_whitespace
|
|
551
|
+
@index += 1 while @index < @text.length && " \t\r\n".include?(@text[@index])
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
def current_character
|
|
555
|
+
@text[@index]
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
def digit?(character)
|
|
559
|
+
character && character.match?(/[0-9]/)
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
def invalid!
|
|
563
|
+
raise InvalidResponseError.new("ERPC returned an invalid Bitcoin response"), cause: nil
|
|
564
|
+
end
|
|
565
|
+
end
|
|
566
|
+
private_constant :Scanner
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
BITCOIN_POLICY_ID = "bitcoin-exact-json-v1".freeze
|
|
570
|
+
BITCOIN_CODEC = BitcoinCodec
|
|
571
|
+
|
|
572
|
+
private_constant :BitcoinResponseLimits, :BITCOIN_RESPONSE_LIMITS, :BitcoinNumberTuple,
|
|
573
|
+
:BitcoinPreflightResult, :BitcoinNumberToken, :BitcoinCodec, :BITCOIN_CODEC
|
|
574
|
+
end
|