scale_rb 0.4.2 → 0.5.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/.devcontainer/devcontainer.json +21 -0
- data/Dockerfile +16 -0
- data/Gemfile +4 -4
- data/README.md +19 -7
- data/bin/console +0 -0
- data/bin/setup +0 -0
- data/examples/http_client_2.rb +0 -2
- data/exe/metadata +9 -11
- data/lib/address.rb +1 -1
- data/lib/custom_assign.rb +92 -0
- data/lib/scale_rb/call_helper.rb +42 -0
- data/lib/{client → scale_rb/client}/client_ext.rb +12 -13
- data/lib/{client → scale_rb/client}/http_client.rb +1 -1
- data/lib/{client → scale_rb/client}/ws_client.rb +11 -15
- data/lib/scale_rb/codec.rb +25 -0
- data/lib/scale_rb/codec_utils.rb +128 -0
- data/lib/scale_rb/decode.rb +164 -0
- data/lib/scale_rb/encode.rb +150 -0
- data/lib/{hasher.rb → scale_rb/hasher.rb} +10 -8
- data/lib/scale_rb/metadata/metadata.rb +114 -0
- data/lib/{metadata → scale_rb/metadata}/metadata_v10.rb +0 -17
- data/lib/{metadata → scale_rb/metadata}/metadata_v11.rb +0 -17
- data/lib/{metadata → scale_rb/metadata}/metadata_v12.rb +0 -17
- data/lib/{metadata → scale_rb/metadata}/metadata_v13.rb +0 -17
- data/lib/{metadata → scale_rb/metadata}/metadata_v14.rb +18 -18
- data/lib/{metadata → scale_rb/metadata}/metadata_v9.rb +0 -17
- data/lib/scale_rb/metadata/registry.rb +263 -0
- data/lib/scale_rb/metadata/type_exp.rb +286 -0
- data/lib/scale_rb/portable_registry.rb +133 -0
- data/lib/{storage_helper.rb → scale_rb/storage_helper.rb} +16 -4
- data/lib/scale_rb/types.rb +233 -0
- data/lib/scale_rb/utils.rb +125 -0
- data/lib/scale_rb/version.rb +1 -1
- data/lib/scale_rb.rb +20 -26
- data/lib/type_enforcer.rb +170 -0
- data/scale_rb.gemspec +3 -0
- metadata +57 -19
- data/lib/codec.rb +0 -450
- data/lib/metadata/metadata.rb +0 -137
- data/lib/monkey_patching.rb +0 -115
- data/lib/portable_codec.rb +0 -285
- data/lib/registry.rb +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cb19be98106d9f8331a9be9812f6252c23d6913cbd98cedcae34bf1a464e7b90
|
4
|
+
data.tar.gz: cff1e034d85f130ed7d149628647f020adb9ffc532d9646bceda054007d6bf14
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: de658214901aa13071c6b8d0c1aa94c8db0b2cee3c6c1df3207eb0881399c12215ce6c05a7d89c7da115e279a60fbe4688710e5f3fba827d5bd729afb7275e59
|
7
|
+
data.tar.gz: 999f5311c1a66933281798c40bbb3b7956e528b3164c264f2a7694fa48c142683e49ff47a185c1e174501b2e8c3b083d2bd513d1e6c37cffff9002d9186908e4
|
@@ -0,0 +1,21 @@
|
|
1
|
+
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
2
|
+
// README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-dockerfile
|
3
|
+
{
|
4
|
+
"name": "scale_rb",
|
5
|
+
"build": {
|
6
|
+
// Sets the run context to one level up instead of the .devcontainer folder.
|
7
|
+
"context": "..",
|
8
|
+
// Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename.
|
9
|
+
"dockerfile": "../Dockerfile"
|
10
|
+
}
|
11
|
+
// Features to add to the dev container. More info: https://containers.dev/features.
|
12
|
+
// "features": {},
|
13
|
+
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
14
|
+
// "forwardPorts": [],
|
15
|
+
// Uncomment the next line to run commands after the container is created.
|
16
|
+
// "postCreateCommand": "cat /etc/os-release",
|
17
|
+
// Configure tool-specific properties.
|
18
|
+
// "customizations": {},
|
19
|
+
// Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root.
|
20
|
+
// "remoteUser": "devcontainer"
|
21
|
+
}
|
data/Dockerfile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
FROM ruby:3.1
|
2
|
+
|
3
|
+
# Dependencies
|
4
|
+
RUN apt-get update -qq && apt-get install -y build-essential
|
5
|
+
RUN curl https://sh.rustup.rs -sSf | bash -s -- -y
|
6
|
+
ENV PATH="/root/.cargo/bin:${PATH}"
|
7
|
+
|
8
|
+
# Set the working directory
|
9
|
+
WORKDIR /scale_rb
|
10
|
+
COPY . /scale_rb
|
11
|
+
|
12
|
+
# Intall gems
|
13
|
+
RUN bundle install
|
14
|
+
|
15
|
+
# default command is to run a shell
|
16
|
+
CMD ["bash"]
|
data/Gemfile
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
source
|
1
|
+
source 'https://rubygems.org'
|
2
2
|
|
3
3
|
# Specify your gem's dependencies in scale_rb.gemspec
|
4
4
|
gemspec
|
5
5
|
|
6
|
-
gem
|
7
|
-
gem
|
8
|
-
gem 'rubocop', group: 'development', require: false
|
6
|
+
gem 'rake', '~> 12.0'
|
7
|
+
gem 'rspec', '~> 3.0'
|
8
|
+
gem 'rubocop', group: 'development', require: false
|
data/README.md
CHANGED
@@ -1,13 +1,11 @@
|
|
1
1
|
# ScaleRb
|
2
2
|
|
3
|
-
It is still under heavy development. Use the latest version.
|
4
|
-
|
5
3
|
## Installation
|
6
4
|
|
7
5
|
Add this line to your application's Gemfile:
|
8
6
|
|
9
7
|
```ruby
|
10
|
-
gem
|
8
|
+
gem "scale_rb"
|
11
9
|
```
|
12
10
|
|
13
11
|
And then execute:
|
@@ -25,20 +23,34 @@ git clone https://github.com/wuminzhe/scale_rb.git
|
|
25
23
|
cd scale_rb
|
26
24
|
bundle install
|
27
25
|
bundle exec ruby examples/http_client_1.rb
|
28
|
-
# CONSOLE_LEVEL=debug bundle exec ruby examples/http_client_1.rb
|
29
26
|
```
|
30
27
|
|
31
28
|
## Development
|
32
29
|
|
33
|
-
|
30
|
+
### Run devcontainer
|
31
|
+
|
32
|
+
Open the project in vscode, then in the command palette, type `Reopen in Container` to open the project in a devcontainer.
|
33
|
+
|
34
|
+

|
35
|
+
|
34
36
|
|
35
|
-
|
37
|
+
After the devcontainer is opened, you can run the following commands:
|
38
|
+
|
39
|
+
1. Tests:
|
40
|
+
|
41
|
+
```bash
|
42
|
+
bundle exec rspec
|
43
|
+
```
|
44
|
+
|
45
|
+
2. Examples:
|
46
|
+
```bash
|
47
|
+
bundle exec ruby examples/http_client_1.rb
|
48
|
+
```
|
36
49
|
|
37
50
|
## Contributing
|
38
51
|
|
39
52
|
Bug reports and pull requests are welcome on GitHub at https://github.com/wuminzhe/scale_rb. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/wuminzhe/scale_rb/blob/master/CODE_OF_CONDUCT.md).
|
40
53
|
|
41
|
-
|
42
54
|
## License
|
43
55
|
|
44
56
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/bin/console
CHANGED
File without changes
|
data/bin/setup
CHANGED
File without changes
|
data/examples/http_client_2.rb
CHANGED
@@ -14,7 +14,5 @@ def fetch_some_storages(client, block_number)
|
|
14
14
|
puts "Time taken: #{end_time - start_time} seconds"
|
15
15
|
end
|
16
16
|
|
17
|
-
ScaleRb.logger.level = Logger::DEBUG
|
18
|
-
|
19
17
|
client = ScaleRb::HttpClient.new('https://polkadot-rpc.dwellir.com')
|
20
18
|
fetch_some_storages(client, 21711742)
|
data/exe/metadata
CHANGED
@@ -5,13 +5,9 @@ require 'scale_rb'
|
|
5
5
|
require 'json'
|
6
6
|
require 'optparse'
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
# ./bin/metadata http://g2.dev.darwinia.network:2234 -b 0x23ebddd6519aaf1b7fc916c3709af13d7a4010943fb53038406581171000a58e
|
13
|
-
# ./bin/metadata https://rpc.darwinia.network -b 10511703
|
14
|
-
# ./bin/metadata https://pangoro-rpc.darwinia.network
|
8
|
+
# ./exe/metadata https://polkadot-rpc.dwellir.com -b 0xfc2d86c5a2cc92b7770dcc14e74116912c9f848000b5f4ceca6fe1475588692c
|
9
|
+
# ./exe/metadata https://polkadot-rpc.dwellir.com -b 22931689
|
10
|
+
# ./exe/metadata https://polkadot-rpc.dwellir.com
|
15
11
|
@options = {}
|
16
12
|
parser = OptionParser.new do |opts|
|
17
13
|
opts.banner = 'Usage: metadata [url] [options]'
|
@@ -26,16 +22,18 @@ else
|
|
26
22
|
parser.parse!
|
27
23
|
url = ARGV[0]
|
28
24
|
|
25
|
+
client = ScaleRb::HttpClient.new(url)
|
26
|
+
|
27
|
+
block_hash = nil
|
29
28
|
if @options[:block]
|
30
29
|
if @options[:block].length == 66
|
31
30
|
block_hash = @options[:block]
|
32
31
|
else
|
33
32
|
block_number = @options[:block].to_i
|
34
|
-
block_hash =
|
33
|
+
block_hash = client.chain_getBlockHash(block_number)
|
35
34
|
end
|
36
|
-
else
|
37
|
-
block_hash = ScaleRb::HttpClient.chain_getBlockHash url
|
38
35
|
end
|
39
36
|
|
40
|
-
|
37
|
+
metadata = client.get_metadata(block_hash)
|
38
|
+
puts JSON.pretty_generate(metadata)
|
41
39
|
end
|
data/lib/address.rb
CHANGED
@@ -79,7 +79,7 @@ module ScaleRb
|
|
79
79
|
end
|
80
80
|
|
81
81
|
input_bytes = ss58_format_bytes.bytes + pubkey_bytes
|
82
|
-
checksum = Blake2b.hex(SS58_PREFIX.bytes + input_bytes, 64)
|
82
|
+
checksum = Utils.hex_to_u8a(Blake2b.hex(SS58_PREFIX.bytes + input_bytes, 64))
|
83
83
|
|
84
84
|
Base58.binary_to_base58((input_bytes + checksum[0...checksum_length]).pack('C*'), :bitcoin)
|
85
85
|
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
def custom_assign(positional_params, keyword_params, args, kwargs = {}, defaults = {})
|
2
|
+
assigned = {}
|
3
|
+
|
4
|
+
# Handle positional arguments (with optional defaults)
|
5
|
+
raise ArgumentError, 'Too many positional arguments' if args.length > positional_params.length
|
6
|
+
|
7
|
+
positional_params.each_with_index do |param, index|
|
8
|
+
if args[index]
|
9
|
+
assigned[param] = args[index] # Assign from args if available
|
10
|
+
elsif args.length == positional_params.length
|
11
|
+
assigned[param] = nil
|
12
|
+
elsif defaults.key?(param)
|
13
|
+
assigned[param] = defaults[param] # Assign default if no argument is provided
|
14
|
+
else
|
15
|
+
raise ArgumentError, "Missing required positional argument: #{param}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Handle keyword arguments (with optional defaults)
|
20
|
+
kwargs.each_key do |key|
|
21
|
+
raise ArgumentError, "Unknown keyword argument: #{key}" unless keyword_params.include?(key)
|
22
|
+
end
|
23
|
+
|
24
|
+
keyword_params.each do |key|
|
25
|
+
if kwargs.key?(key)
|
26
|
+
assigned[key] = kwargs[key] # Assign from kwargs if available
|
27
|
+
elsif defaults.key?(key)
|
28
|
+
assigned[key] = defaults[key] # Assign default if not provided
|
29
|
+
else
|
30
|
+
raise ArgumentError, "Missing required keyword argument: #{key}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# assert_equal(positional_params.length + keyword_params.length, assigned.length)
|
35
|
+
assigned
|
36
|
+
end
|
37
|
+
|
38
|
+
def get_method_params(method)
|
39
|
+
params = method.parameters
|
40
|
+
|
41
|
+
positional_params = []
|
42
|
+
keyword_params = []
|
43
|
+
|
44
|
+
params.each do |type, name|
|
45
|
+
case type
|
46
|
+
when :req, :opt # Required, optional positional arguments
|
47
|
+
positional_params << name
|
48
|
+
when :key, :keyreq # Keyword arguments
|
49
|
+
keyword_params << name
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
[positional_params, keyword_params]
|
54
|
+
end
|
55
|
+
|
56
|
+
def assert_equal(expected, actual)
|
57
|
+
raise "Expected #{expected}, but got #{actual}" unless expected === actual
|
58
|
+
end
|
59
|
+
|
60
|
+
def build_assigned_params(method, defaults, args, kwargs)
|
61
|
+
positional_params, keyword_params = get_method_params(method)
|
62
|
+
custom_assign(positional_params, keyword_params, args, kwargs, defaults)
|
63
|
+
end
|
64
|
+
|
65
|
+
# def my_method(a, b = 2, c:, d: 4); end
|
66
|
+
|
67
|
+
# defaults = { b: 2, d: 4 }
|
68
|
+
|
69
|
+
# # my_method(5, c: 3)
|
70
|
+
# p build_assigned_params(:my_method, defaults, 5, c: 3)
|
71
|
+
# # => {:a=>5, :b=>2, :c=>3, :d=>4}
|
72
|
+
|
73
|
+
# # my_method(5, 6, c: 3)
|
74
|
+
# p build_assigned_params(:my_method, defaults, 5, 6, c: 3)
|
75
|
+
# # => {:a=>5, :b=>6, :c=>3, :d=>4}
|
76
|
+
|
77
|
+
# # my_method(5, 6, c: 3, d: 10)
|
78
|
+
# p build_assigned_params(:my_method, defaults, 5, 6, c: 3, d: 10)
|
79
|
+
# # => {:a=>5, :b=>6, :c=>3, :d=>10}
|
80
|
+
|
81
|
+
# # my_method(5, 6, c: 3, d: 10, e: 11)
|
82
|
+
# begin
|
83
|
+
# build_assigned_params(:my_method, defaults, 5, 6, c: 3, d: 10, e: 11)
|
84
|
+
# rescue ArgumentError => e
|
85
|
+
# puts e.message # => "Unknown keyword argument: e"
|
86
|
+
# end
|
87
|
+
|
88
|
+
# begin
|
89
|
+
# build_assigned_params(:my_method, defaults, 5, 6, 7, c: 3, d: 10)
|
90
|
+
# rescue ArgumentError => e
|
91
|
+
# puts e.message # => "Too many positional arguments"
|
92
|
+
# end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ScaleRb
|
4
|
+
module CallHelper
|
5
|
+
# callbytes's structure is: pallet_index + call_index + argsbytes
|
6
|
+
#
|
7
|
+
# callbytes examples:
|
8
|
+
# "0x0901"._to_bytes
|
9
|
+
# "0x05000a1287977578f888bdc1c7627781af1cc000e6ab1300004c31b8d9a798"._to_bytes
|
10
|
+
def self.decode_call(callbytes, metadata)
|
11
|
+
pallet_index = callbytes[0]
|
12
|
+
pallet = Metadata.get_module_by_index(pallet_index, metadata)
|
13
|
+
|
14
|
+
# Remove the pallet_index
|
15
|
+
# The callbytes we used below should not contain the pallet index.
|
16
|
+
# This is because the pallet index is not part of the call type.
|
17
|
+
# Its structure is: call_index + call_args
|
18
|
+
callbytes_without_pallet_index = callbytes[1..]
|
19
|
+
calls_type_id = pallet._get(:calls, :type)
|
20
|
+
decoded = Codec.decode(
|
21
|
+
calls_type_id,
|
22
|
+
callbytes_without_pallet_index,
|
23
|
+
Metadata.build_registry(metadata)
|
24
|
+
)&.first
|
25
|
+
|
26
|
+
{
|
27
|
+
pallet_name: pallet._get(:name),
|
28
|
+
call_name: decoded.is_a?(::Hash) ? decoded.keys.first.to_s : decoded.to_s,
|
29
|
+
call: decoded
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
# call examples:
|
34
|
+
# {:pallet_name=>"Deposit", :call_name=>"claim", :call=>:claim]}
|
35
|
+
# {:pallet_name=>"Balances", :call_name=>"transfer", :call=>{:transfer=>{:dest=>[10, 18, 135, 151, 117, 120, 248, 136, 189, 193, 199, 98, 119, 129, 175, 28, 192, 0, 230, 171], :value=>11000000000000000000}}]}
|
36
|
+
def self.encode_call(call, metadata)
|
37
|
+
calls_type_id = Metadata.get_calls_type_id(call[:pallet_name], metadata)
|
38
|
+
pallet_index = Metadata.get_module(call[:pallet_name], metadata)._get(:index)
|
39
|
+
[pallet_index] + Codec.encode(calls_type_id, call[:call], Metadata.build_registry(metadata))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -1,12 +1,11 @@
|
|
1
1
|
module ScaleRb
|
2
|
-
|
3
2
|
# This module is used to add extra methods to both the ScaleRb::WsClient ScaleRb::HttpClient
|
4
3
|
module ClientExt
|
5
4
|
# get decoded metadata at block_hash
|
6
5
|
def get_metadata(block_hash = nil)
|
7
6
|
block_hash ||= chain_getHead
|
8
7
|
metadata_hex = state_getMetadata(block_hash)
|
9
|
-
|
8
|
+
Metadata.decode_metadata(metadata_hex)
|
10
9
|
end
|
11
10
|
|
12
11
|
# Get decoded storage at block_hash
|
@@ -39,8 +38,8 @@ module ScaleRb
|
|
39
38
|
item[:changes].map do |change|
|
40
39
|
storage_key = change[0]
|
41
40
|
data = change[1] || default
|
42
|
-
storage = data.nil? ? nil :
|
43
|
-
{ storage_key
|
41
|
+
storage = data.nil? ? nil : Codec.decode(type_id, Utils.hex_to_u8a(data), registry)[0]
|
42
|
+
{ storage_key:, storage: }
|
44
43
|
end
|
45
44
|
end.flatten
|
46
45
|
end
|
@@ -83,7 +82,7 @@ module ScaleRb
|
|
83
82
|
# 'System',
|
84
83
|
# 'Account',
|
85
84
|
# key = {
|
86
|
-
# value: [['0x724d50824542b56f422588421643c4a162b90b5416ef063f2266a1eae6651641'
|
85
|
+
# value: [[Utils.hex_to_u8a('0x724d50824542b56f422588421643c4a162b90b5416ef063f2266a1eae6651641')]], # [AccountId]
|
87
86
|
# type: 0,
|
88
87
|
# hashers: ['Blake2128Concat']
|
89
88
|
# },
|
@@ -101,7 +100,7 @@ module ScaleRb
|
|
101
100
|
if key
|
102
101
|
if key[:value].nil? || key[:value].empty?
|
103
102
|
# map, but no key's value provided. get all storages under the partial storage key
|
104
|
-
partial_storage_key = StorageHelper.encode_storage_key(pallet_name, item_name)
|
103
|
+
partial_storage_key = Utils.u8a_to_hex(StorageHelper.encode_storage_key(pallet_name, item_name))
|
105
104
|
get_storages_by_partial_key(
|
106
105
|
block_hash,
|
107
106
|
partial_storage_key,
|
@@ -111,7 +110,8 @@ module ScaleRb
|
|
111
110
|
)
|
112
111
|
elsif key[:value].length != key[:hashers].length
|
113
112
|
# map with multi parts, but not have all values
|
114
|
-
partial_storage_key = StorageHelper.encode_storage_key(pallet_name, item_name, key,
|
113
|
+
partial_storage_key = Utils.u8a_to_hex(StorageHelper.encode_storage_key(pallet_name, item_name, key,
|
114
|
+
registry))
|
115
115
|
get_storages_by_partial_key(
|
116
116
|
block_hash,
|
117
117
|
partial_storage_key,
|
@@ -120,12 +120,12 @@ module ScaleRb
|
|
120
120
|
registry
|
121
121
|
)
|
122
122
|
else
|
123
|
-
storage_key = StorageHelper.encode_storage_key(pallet_name, item_name, key, registry)
|
123
|
+
storage_key = Utils.u8a_to_hex(StorageHelper.encode_storage_key(pallet_name, item_name, key, registry))
|
124
124
|
data = state_getStorage(storage_key, block_hash)
|
125
125
|
StorageHelper.decode_storage(data, value[:type], value[:modifier] == 'Optional', value[:fallback], registry)
|
126
126
|
end
|
127
127
|
else
|
128
|
-
storage_key = StorageHelper.encode_storage_key(pallet_name, item_name)
|
128
|
+
storage_key = Utils.u8a_to_hex(StorageHelper.encode_storage_key(pallet_name, item_name))
|
129
129
|
data = state_getStorage(storage_key, block_hash)
|
130
130
|
StorageHelper.decode_storage(data, value[:type], value[:modifier] == 'Optional', value[:fallback], registry)
|
131
131
|
end
|
@@ -152,12 +152,12 @@ module ScaleRb
|
|
152
152
|
if plain
|
153
153
|
[
|
154
154
|
nil,
|
155
|
-
{ type: plain, modifier
|
155
|
+
{ type: plain, modifier:, fallback: }
|
156
156
|
]
|
157
157
|
elsif map
|
158
158
|
[
|
159
159
|
{ value: params, type: map._get(:key), hashers: map._get(:hashers) },
|
160
|
-
{ type: map._get(:value), modifier
|
160
|
+
{ type: map._get(:value), modifier:, fallback: }
|
161
161
|
]
|
162
162
|
else
|
163
163
|
raise 'NoSuchStorageType'
|
@@ -175,11 +175,10 @@ module ScaleRb
|
|
175
175
|
if key.is_a?(Integer)
|
176
176
|
key.to_i
|
177
177
|
elsif key.is_a?(String) && key.start_with?('0x')
|
178
|
-
key
|
178
|
+
Utils.hex_to_u8a(key)
|
179
179
|
else
|
180
180
|
key
|
181
181
|
end
|
182
182
|
end
|
183
|
-
|
184
183
|
end
|
185
184
|
end
|
@@ -30,7 +30,7 @@ module ScaleRb
|
|
30
30
|
http.use_ssl = @uri.scheme == 'https'
|
31
31
|
|
32
32
|
request = Net::HTTP::Post.new(@uri, 'Content-Type' => 'application/json')
|
33
|
-
request.body = { jsonrpc: '2.0', method
|
33
|
+
request.body = { jsonrpc: '2.0', method:, params:, id: Time.now.to_i }.to_json
|
34
34
|
ScaleRb.logger.debug "—→ #{request.body}"
|
35
35
|
|
36
36
|
# https://docs.ruby-lang.org/en/master/Net/HTTPResponse.html
|
@@ -6,7 +6,6 @@ require_relative 'client_ext'
|
|
6
6
|
|
7
7
|
module ScaleRb
|
8
8
|
class WsClient
|
9
|
-
|
10
9
|
class << self
|
11
10
|
# @param [string] url
|
12
11
|
def start(url)
|
@@ -29,7 +28,7 @@ module ScaleRb
|
|
29
28
|
end
|
30
29
|
end
|
31
30
|
|
32
|
-
client.supported_methods = client.rpc_methods
|
31
|
+
client.supported_methods = client.rpc_methods[:methods]
|
33
32
|
yield client
|
34
33
|
|
35
34
|
recv_task.wait
|
@@ -37,7 +36,6 @@ module ScaleRb
|
|
37
36
|
recv_task&.stop
|
38
37
|
end
|
39
38
|
end
|
40
|
-
|
41
39
|
end
|
42
40
|
|
43
41
|
private
|
@@ -45,11 +43,10 @@ module ScaleRb
|
|
45
43
|
def parse_message(message)
|
46
44
|
message.parse
|
47
45
|
rescue StandardError => e
|
48
|
-
Console::Event::Failure.for(e).emit(self,
|
46
|
+
Console::Event::Failure.for(e).emit(self, 'Parse message failed!')
|
49
47
|
nil
|
50
48
|
end
|
51
49
|
end
|
52
|
-
|
53
50
|
end
|
54
51
|
end
|
55
52
|
|
@@ -102,9 +99,9 @@ module ScaleRb
|
|
102
99
|
def unsubscribe(method, subscription_id)
|
103
100
|
return unless method.include?('unsubscribe')
|
104
101
|
|
105
|
-
|
106
|
-
|
107
|
-
|
102
|
+
return unless @subscription_handler.unsubscribe(subscription_id)
|
103
|
+
|
104
|
+
request(method, [subscription_id])
|
108
105
|
end
|
109
106
|
|
110
107
|
def handle_response(response)
|
@@ -116,14 +113,14 @@ module ScaleRb
|
|
116
113
|
ScaleRb.logger.info "Received an unknown response: #{response}"
|
117
114
|
end
|
118
115
|
rescue StandardError => e
|
119
|
-
Console::Event::Failure.for(e).emit(self,
|
116
|
+
Console::Event::Failure.for(e).emit(self, 'Handle response failed!')
|
120
117
|
end
|
121
118
|
|
122
119
|
def read_message
|
123
120
|
loop do
|
124
121
|
return @connection.read
|
125
122
|
rescue StandardError => e
|
126
|
-
Console::Event::Failure.for(e).emit(self,
|
123
|
+
Console::Event::Failure.for(e).emit(self, 'Read message from connection failed!')
|
127
124
|
sleep 1
|
128
125
|
retry
|
129
126
|
end
|
@@ -138,7 +135,7 @@ module ScaleRb
|
|
138
135
|
response_future.resolve(response[:result])
|
139
136
|
})
|
140
137
|
|
141
|
-
request = { jsonrpc: '2.0', id: @request_id, method
|
138
|
+
request = { jsonrpc: '2.0', id: @request_id, method:, params: }
|
142
139
|
ScaleRb.logger.debug "—→ #{request}"
|
143
140
|
@connection.write(request.to_json)
|
144
141
|
|
@@ -186,10 +183,9 @@ module ScaleRb
|
|
186
183
|
subscription_id = notification.dig(:params, :subscription)
|
187
184
|
return if subscription_id.nil?
|
188
185
|
|
189
|
-
|
190
|
-
|
191
|
-
|
186
|
+
return unless @callbacks.key?(subscription_id)
|
187
|
+
|
188
|
+
@callbacks[subscription_id].call(notification)
|
192
189
|
end
|
193
190
|
end
|
194
|
-
|
195
191
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'decode'
|
4
|
+
require_relative 'encode'
|
5
|
+
|
6
|
+
module ScaleRb
|
7
|
+
module Codec
|
8
|
+
class Error < StandardError; end
|
9
|
+
class TypeNotFound < Error; end
|
10
|
+
class TypeNotImplemented < Error; end
|
11
|
+
class CompositeInvalidValue < Error; end
|
12
|
+
class ArrayLengthNotEqual < Error; end
|
13
|
+
class VariantItemNotFound < Error; end
|
14
|
+
class VariantIndexOutOfRange < Error; end
|
15
|
+
class VariantInvalidValue < Error; end
|
16
|
+
class VariantFieldsLengthNotMatch < Error; end
|
17
|
+
class LengthNotEqualErr < Error; end
|
18
|
+
class NotEnoughBytesError < Error; end
|
19
|
+
class Unreachable < Error; end
|
20
|
+
class InvalidBytesError < Error; end
|
21
|
+
|
22
|
+
extend Decode
|
23
|
+
extend Encode
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ScaleRb
|
4
|
+
module CodecUtils
|
5
|
+
module InternalDecodeUtils
|
6
|
+
extend TypeEnforcer
|
7
|
+
include Types
|
8
|
+
|
9
|
+
__ :decode_uint,
|
10
|
+
{ type: String.constrained(format: /\A[Uu]\d+\z/), bytes: U8Array },
|
11
|
+
DecodeResult[UnsignedInteger]
|
12
|
+
def decode_uint(type, bytes)
|
13
|
+
bit_length = type[1..].to_i
|
14
|
+
byte_length = bit_length / 8
|
15
|
+
raise Codec::NotEnoughBytesError, "type: #{type}" if bytes.length < byte_length
|
16
|
+
|
17
|
+
value = Utils.u8a_to_uint(bytes[0...byte_length].reverse)
|
18
|
+
# debug 'value', value
|
19
|
+
[
|
20
|
+
value,
|
21
|
+
bytes[byte_length..]
|
22
|
+
]
|
23
|
+
end
|
24
|
+
|
25
|
+
__ :decode_int, { type: String.constrained(format: /\A[Ii]\d+\z/), bytes: U8Array }, DecodeResult[Integer]
|
26
|
+
def decode_int(type, bytes)
|
27
|
+
bit_length = type[1..].to_i
|
28
|
+
byte_length = bit_length / 8
|
29
|
+
raise Codec::NotEnoughBytesError, "type: #{type}" if bytes.length < byte_length
|
30
|
+
|
31
|
+
value = Utils.u8a_to_int(bytes[0...byte_length].reverse, bit_length)
|
32
|
+
# debug 'value', value
|
33
|
+
[
|
34
|
+
value,
|
35
|
+
bytes[byte_length..]
|
36
|
+
]
|
37
|
+
end
|
38
|
+
|
39
|
+
__ :decode_str, { bytes: U8Array }, DecodeResult[String]
|
40
|
+
def decode_str(bytes)
|
41
|
+
length, remaining_bytes = _do_decode_compact(bytes)
|
42
|
+
raise Codec::NotEnoughBytesError, 'type: String' if remaining_bytes.length < length
|
43
|
+
|
44
|
+
[Utils.u8a_to_utf8(remaining_bytes[0...length]), remaining_bytes[length..]]
|
45
|
+
end
|
46
|
+
|
47
|
+
__ :decode_boolean, { bytes: U8Array }, DecodeResult[Bool]
|
48
|
+
def decode_boolean(bytes)
|
49
|
+
value = case bytes[0]
|
50
|
+
when 0x00 then false
|
51
|
+
when 0x01 then true
|
52
|
+
else raise Codec::InvalidBytesError, 'type: Boolean'
|
53
|
+
end
|
54
|
+
[value, bytes[1..]]
|
55
|
+
end
|
56
|
+
|
57
|
+
# TODO: inner type decoding
|
58
|
+
__ :decode_compact, { bytes: U8Array }, DecodeResult[UnsignedInteger]
|
59
|
+
def decode_compact(bytes)
|
60
|
+
_do_decode_compact(bytes)
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def _do_decode_compact(bytes)
|
66
|
+
case bytes[0] & 3
|
67
|
+
when 0 then [bytes[0] >> 2, bytes[1..]]
|
68
|
+
when 1 then [Utils.u8a_to_uint(bytes[0..1].reverse) >> 2, bytes[2..]]
|
69
|
+
when 2 then [Utils.u8a_to_uint(bytes[0..3].reverse) >> 2, bytes[4..]]
|
70
|
+
when 3
|
71
|
+
length = 4 + (bytes[0] >> 2)
|
72
|
+
[Utils.u8a_to_uint(bytes[1..length].reverse), bytes[length + 1..]]
|
73
|
+
else
|
74
|
+
raise Codec::Unreachable, 'type: Compact'
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
module InternalEncodeUtils
|
80
|
+
extend TypeEnforcer
|
81
|
+
include Types
|
82
|
+
|
83
|
+
__ :encode_uint, { type: String.constrained(format: /\A[Uu]\d+\z/), value: Integer }, U8Array
|
84
|
+
def encode_uint(type, value)
|
85
|
+
raise InvalidValueError, "type: #{type}, value: #{value.inspect}" unless value.is_a?(::Integer)
|
86
|
+
|
87
|
+
bit_length = type[1..].to_i
|
88
|
+
Utils.int_to_u8a(value, bit_length).reverse
|
89
|
+
end
|
90
|
+
|
91
|
+
__ :encode_int, { type: String.constrained(format: /\A[Ii]\d+\z/), value: Integer }, U8Array
|
92
|
+
def encode_int(_type, _value)
|
93
|
+
raise NotImplemented, 'encode_int'
|
94
|
+
# raise InvalidValueError, "type: #{type}, value: #{value.inspect}" unless value.is_a?(Integer)
|
95
|
+
#
|
96
|
+
# bit_length = type[1..].to_i
|
97
|
+
# Utils.int_to_u8a(value, bit_length).reverse
|
98
|
+
end
|
99
|
+
|
100
|
+
__ :encode_str, { string: String }, U8Array
|
101
|
+
def encode_str(string)
|
102
|
+
body = string.unpack('C*')
|
103
|
+
encode_compact(body.length) + body
|
104
|
+
end
|
105
|
+
|
106
|
+
__ :encode_boolean, { value: Bool }, U8Array
|
107
|
+
def encode_boolean(value)
|
108
|
+
return [0x00] if value == false
|
109
|
+
return [0x01] if value == true
|
110
|
+
|
111
|
+
raise InvalidValueError, "type: Bool, value: #{value.inspect}"
|
112
|
+
end
|
113
|
+
|
114
|
+
__ :encode_compact, { value: Integer }, U8Array
|
115
|
+
def encode_compact(value)
|
116
|
+
return [value << 2] if value.between?(0, 63)
|
117
|
+
return Utils.int_to_u8a(((value << 2) + 1)).reverse if value < 2**14
|
118
|
+
return Utils.int_to_u8a(((value << 2) + 2)).reverse if value < 2**30
|
119
|
+
|
120
|
+
bytes = Utils.int_to_u8a(value).reverse
|
121
|
+
[(((bytes.length - 4) << 2) + 3)] + bytes
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
extend InternalDecodeUtils
|
126
|
+
extend InternalEncodeUtils
|
127
|
+
end
|
128
|
+
end
|