scale_rb 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9857ea16c7327b90a598f40ce8afe919ee3db3a1fada67ba9e1a5ee768c50fdf
4
- data.tar.gz: 7bd319b972c1c4aa13f4f24c6af525294bcbd8b6361423db66ac6038e44ad990
3
+ metadata.gz: 79d55aeb610c1a4a11410703445983aa6b02cbf8ee21653a38d425b06c41e3dc
4
+ data.tar.gz: 8e7832aa2495fed7226797bd21bc23db257754c8d5160cba57f7830879fe592f
5
5
  SHA512:
6
- metadata.gz: db6452725e87b88cd0bdffe419588c8b9b8cfe892bdac01df226e9008bf02e45d56c9c25447a932d8ddf43bdfecf6ade0095710c9e056ee14ef2813f7b500b75
7
- data.tar.gz: 2c6d01fd6726cc7ff5ed7c303b5e3c7ffe48347267a7b6989a6cda032741c47cfe339fa4c51871166b78f794a999cf8b0e6104cf44d013912bf5dbac0599cb45
6
+ metadata.gz: 879a524038c25c42b98724a95c9db2658ba41a952386fa4132eb56ceff4d3403aa3fc519c4add1d29fc1ad6e413bdb5f1bc8ab4585a836327bc233dc7750ab43
7
+ data.tar.gz: 4529731208f640854a9b9b3676ffdd3727b9344833ab026cd593f5d10e17ea0428bd651bc01aa774cb80fd68350249ba68be4b99eea54db6fe597b6f3732723a
data/.gitignore CHANGED
@@ -10,3 +10,4 @@
10
10
 
11
11
  # rspec failure tracking
12
12
  .rspec_status
13
+ /metadata
data/.rspec CHANGED
File without changes
data/.rubocop.yml CHANGED
File without changes
data/.travis.yml CHANGED
File without changes
data/CODE_OF_CONDUCT.md CHANGED
File without changes
data/Gemfile CHANGED
File without changes
data/Gemfile.lock CHANGED
@@ -1,7 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- scale_rb (0.2.0)
4
+ scale_rb (0.3.0)
5
+ async
6
+ async-http (~> 0.69.0)
7
+ async-websocket (~> 0.26.2)
5
8
  base58
6
9
  blake2b_rs (~> 0.1.4)
7
10
  xxhash
@@ -9,13 +12,57 @@ PATH
9
12
  GEM
10
13
  remote: https://rubygems.org/
11
14
  specs:
15
+ async (2.14.0)
16
+ console (~> 1.25, >= 1.25.2)
17
+ fiber-annotation
18
+ io-event (~> 1.6, >= 1.6.5)
19
+ async-http (0.69.0)
20
+ async (>= 2.10.2)
21
+ async-pool (~> 0.7)
22
+ io-endpoint (~> 0.11)
23
+ io-stream (~> 0.4)
24
+ protocol-http (~> 0.26)
25
+ protocol-http1 (~> 0.19)
26
+ protocol-http2 (~> 0.18)
27
+ traces (>= 0.10)
28
+ async-pool (0.7.0)
29
+ async (>= 1.25)
30
+ async-websocket (0.26.2)
31
+ async-http (~> 0.54)
32
+ protocol-rack (~> 0.5)
33
+ protocol-websocket (~> 0.14)
12
34
  base58 (0.2.3)
13
35
  blake2b_rs (0.1.4)
14
36
  ffi (~> 1.0)
15
37
  thermite (~> 0)
38
+ console (1.25.2)
39
+ fiber-annotation
40
+ fiber-local (~> 1.1)
41
+ json
16
42
  diff-lcs (1.5.0)
17
- ffi (1.16.3)
43
+ ffi (1.17.0)
44
+ fiber-annotation (0.2.0)
45
+ fiber-local (1.1.0)
46
+ fiber-storage
47
+ fiber-storage (0.1.2)
48
+ io-endpoint (0.11.0)
49
+ io-event (1.6.5)
50
+ io-stream (0.4.0)
51
+ json (2.7.2)
18
52
  minitar (0.9)
53
+ protocol-hpack (1.4.3)
54
+ protocol-http (0.27.0)
55
+ protocol-http1 (0.19.1)
56
+ protocol-http (~> 0.22)
57
+ protocol-http2 (0.18.0)
58
+ protocol-hpack (~> 1.4)
59
+ protocol-http (~> 0.18)
60
+ protocol-rack (0.6.0)
61
+ protocol-http (~> 0.23)
62
+ rack (>= 1.0)
63
+ protocol-websocket (0.15.0)
64
+ protocol-http (~> 0.2)
65
+ rack (3.1.7)
19
66
  rake (12.3.3)
20
67
  rspec (3.11.0)
21
68
  rspec-core (~> 3.11.0)
@@ -35,6 +82,7 @@ GEM
35
82
  rake (>= 10)
36
83
  tomlrb (~> 1.2)
37
84
  tomlrb (1.3.0)
85
+ traces (0.11.1)
38
86
  xxhash (0.5.0)
39
87
 
40
88
  PLATFORMS
data/LICENSE.txt CHANGED
File without changes
data/README.md CHANGED
@@ -1,7 +1,5 @@
1
1
  # ScaleRb
2
2
 
3
- *WARNING: UNDER DEVELOPMENT*
4
-
5
3
  ## Installation
6
4
 
7
5
  Add this line to your application's Gemfile:
@@ -18,9 +16,14 @@ Or install it yourself as:
18
16
 
19
17
  $ gem install scale_rb
20
18
 
21
- ## Usage
19
+ ## Run examples
22
20
 
23
- TODO: Write usage instructions here
21
+ ```bash
22
+ git clone https://github.com/wuminzhe/scale_rb.git
23
+ cd scale_rb
24
+ bundle install
25
+ bundle exec ruby examples/http_client_1.rb
26
+ ```
24
27
 
25
28
  ## Development
26
29
 
data/Rakefile CHANGED
File without changes
@@ -0,0 +1,9 @@
1
+
2
+ require 'scale_rb'
3
+
4
+ ScaleRb.logger.level = Logger::DEBUG
5
+
6
+ client = ScaleRb::HttpClient.new('https://polkadot-rpc.dwellir.com')
7
+ block_hash = client.chain_getBlockHash(21585684)
8
+ runtime_version = client.state_getRuntimeVersion(block_hash)
9
+ puts runtime_version
@@ -0,0 +1,9 @@
1
+ require 'scale_rb'
2
+
3
+ ScaleRb.logger.level = Logger::DEBUG
4
+
5
+ client = ScaleRb::HttpClient.new('https://polkadot-rpc.dwellir.com')
6
+ block_number = 21585684
7
+ block_hash = client.chain_getBlockHash(block_number)
8
+ storage = client.get_storage(block_hash, 'System', 'Events')
9
+ puts "block #{block_number}(#{block_hash}) has #{storage.length} events"
@@ -0,0 +1,14 @@
1
+ require 'scale_rb'
2
+
3
+ ScaleRb.logger.level = Logger::DEBUG
4
+
5
+ # the commented code below is the same as the code above
6
+ ScaleRb::WsClient.start('wss://polkadot-rpc.dwellir.com') do |client|
7
+ block_hash = client.chain_getBlockHash(21585684)
8
+ # block_hash = client.request('chain_getBlockHash', [21585684])
9
+
10
+ runtime_version = client.state_getRuntimeVersion(block_hash)
11
+ # runtime_version = client.request('state_getRuntimeVersion', [block_hash])
12
+
13
+ puts runtime_version
14
+ end
@@ -0,0 +1,9 @@
1
+ require 'scale_rb'
2
+
3
+ ScaleRb.logger.level = Logger::DEBUG
4
+
5
+ ScaleRb::WsClient.start('wss://polkadot-rpc.dwellir.com') do |client|
6
+ client.chain_subscribeNewHead do |head|
7
+ puts "Received new head at height: #{head['number'].to_i(16)}"
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ require 'scale_rb'
2
+
3
+ ScaleRb.logger.level = Logger::DEBUG
4
+
5
+ ScaleRb::WsClient.start('wss://polkadot-rpc.dwellir.com') do |client|
6
+ count = 0
7
+
8
+ subscription_id = client.chain_subscribeNewHead do |head|
9
+ count = count + 1
10
+
11
+ if count < 5
12
+ block_number = head['number'].to_i(16)
13
+ block_hash = client.chain_getBlockHash(block_number)
14
+ puts "Received new head at height: #{block_number}, block hash: #{block_hash}"
15
+ else
16
+ client.chain_unsubscribeNewHead(subscription_id)
17
+ end
18
+ end
19
+
20
+ puts "Subscribed to new heads with subscription id: #{subscription_id}"
21
+ end
@@ -0,0 +1,13 @@
1
+ require 'scale_rb'
2
+
3
+ # ScaleRb.logger.level = Logger::DEBUG
4
+
5
+ ScaleRb::WsClient.start('wss://polkadot-rpc.dwellir.com') do |client|
6
+ client.chain_subscribeFinalizedHeads do |head|
7
+ block_number = head['number'].to_i(16)
8
+ block_hash = client.chain_getBlockHash(block_number)
9
+
10
+ storage = client.get_storage(block_hash, 'System', 'Events')
11
+ puts "block #{block_number}(#{block_hash}) has #{storage.length} events"
12
+ end
13
+ end
data/lib/address.rb CHANGED
File without changes
@@ -0,0 +1,214 @@
1
+ module ScaleRb
2
+ module ClientExt
3
+ def get_metadata(block_hash)
4
+ dir = ENV['SCALE_RB_METADATA_DIR'] || File.join(Dir.pwd, 'metadata')
5
+
6
+ get_metadata_by_block_hash(dir, block_hash)
7
+ end
8
+
9
+ # get storage at block_hash
10
+ def get_storage(block_hash, pallet_name, storage_name, key_part1: nil, key_part2: nil)
11
+ metadata = get_metadata(block_hash)
12
+
13
+ # storeage item
14
+ pallet_name = to_pascal pallet_name
15
+ storage_name = to_pascal storage_name
16
+
17
+ # storage param
18
+ key = [key_part1, key_part2].compact
19
+ ScaleRb.logger.debug "#{pallet_name}.#{storage_name}(#{key.join(', ')})"
20
+ key = key.map { |part_of_key| c(part_of_key) }
21
+ ScaleRb.logger.debug "converted key: #{key}"
22
+
23
+ get_storage2(
24
+ block_hash, # at
25
+ pallet_name,
26
+ storage_name,
27
+ key,
28
+ metadata
29
+ )
30
+ end
31
+
32
+ private
33
+
34
+ def get_metadata_by_block_hash(cache_dir, block_hash)
35
+ # Get metadata from cache if it exists
36
+ runtime_version = state_getRuntimeVersion(block_hash)
37
+ spec_name = runtime_version['specName']
38
+ spec_version = runtime_version['specVersion']
39
+ metadata = cached_metadata(spec_name, spec_version, cache_dir)
40
+ return metadata if metadata
41
+
42
+ # Get metadata from node
43
+ metadata_hex = state_getMetadata(block_hash)
44
+ metadata = ScaleRb::Metadata.decode_metadata(metadata_hex.strip._to_bytes)
45
+
46
+ # cache it
47
+ save_metadata_to_file(spec_name, spec_version, metadata, cache_dir)
48
+
49
+ return metadata
50
+ end
51
+
52
+ def cached_metadata(spec_name, spec_version, dir)
53
+ file_path = File.join(dir, "#{spec_name}-#{spec_version}.json")
54
+ return unless File.exist?(file_path)
55
+
56
+ JSON.parse(File.read(file_path))
57
+ end
58
+
59
+ def save_metadata_to_file(spec_name, spec_version, metadata, dir)
60
+ FileUtils.mkdir_p(dir)
61
+
62
+ File.open(File.join(dir, "#{spec_name}-#{spec_version}.json"), 'w') do |f|
63
+ f.write(JSON.pretty_generate(metadata))
64
+ end
65
+ end
66
+
67
+ ####
68
+
69
+ def query_storage_at(block_hash, storage_keys, type_id, default, registry)
70
+ result = state_queryStorageAt(storage_keys, block_hash)
71
+ result.map do |item|
72
+ item['changes'].map do |change|
73
+ storage_key = change[0]
74
+ data = change[1] || default
75
+ storage = data.nil? ? nil : PortableCodec.decode(type_id, data._to_bytes, registry)[0]
76
+ { storage_key: storage_key, storage: storage }
77
+ end
78
+ end.flatten
79
+ end
80
+
81
+ def get_storage_keys_by_partial_key(block_hash, partial_storage_key, start_key = nil)
82
+ storage_keys = state_getKeysPaged(partial_storage_key, 1000, start_key, block_hash)
83
+ if storage_keys.length == 1000
84
+ storage_keys + get_storage_keys_by_partial_key(block_hash, partial_storage_key, storage_keys.last)
85
+ else
86
+ storage_keys
87
+ end
88
+ end
89
+
90
+ def get_storages_by_partial_key(block_hash, partial_storage_key, type_id_of_value, default, registry)
91
+ storage_keys = get_storage_keys_by_partial_key(block_hash, partial_storage_key, partial_storage_key)
92
+ storage_keys.each_slice(250).map do |slice|
93
+ query_storage_at(
94
+ slice,
95
+ type_id_of_value,
96
+ default,
97
+ registry,
98
+ block_hash
99
+ )
100
+ end.flatten
101
+ end
102
+
103
+ # 1. Plain
104
+ # key: nil
105
+ # value: { type: 3, modifier: 'Default', callback: '' }
106
+ #
107
+ # 2. Map
108
+ # key: { value: value, type: 0, hashers: ['Blake2128Concat'] }
109
+ # value: { type: 3, modifier: 'Default', callback: '' }
110
+ #
111
+ # 3. Map, but key.value is nil
112
+ # key: { value: nil, type: 0, hashers: ['Blake2128Concat'] }
113
+ # value: { type: 3, modifier: 'Default', callback: '' }
114
+ #
115
+ # example:
116
+ # 'System',
117
+ # 'Account',
118
+ # key = {
119
+ # value: [['0x724d50824542b56f422588421643c4a162b90b5416ef063f2266a1eae6651641'._to_bytes]], # [AccountId]
120
+ # type: 0,
121
+ # hashers: ['Blake2128Concat']
122
+ # },
123
+ # value = {
124
+ # type: 3,
125
+ # modifier: 'Default',
126
+ # callback: '0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
127
+ # },
128
+ # ..
129
+ #
130
+ # TODO: part of the key is provided, but not all
131
+ def get_storage1(block_hash, pallet_name, item_name, key, value, registry)
132
+ if key
133
+ if key[:value].nil? || key[:value].empty?
134
+ # map, but no key's value provided. get all storages under the partial storage key
135
+ partial_storage_key = StorageHelper.encode_storage_key(pallet_name, item_name)._to_hex
136
+ get_storages_by_partial_key(
137
+ block_hash,
138
+ partial_storage_key,
139
+ value[:type],
140
+ value[:modifier] == 'Default' ? value[:fallback] : nil,
141
+ registry
142
+ )
143
+ elsif key[:value].length != key[:hashers].length
144
+ # map with multi parts, but not have all values
145
+ partial_storage_key = StorageHelper.encode_storage_key(pallet_name, item_name, key, registry)._to_hex
146
+ get_storages_by_partial_key(
147
+ block_hash,
148
+ partial_storage_key,
149
+ value[:type],
150
+ value[:modifier] == 'Default' ? value[:fallback] : nil,
151
+ registry
152
+ )
153
+ end
154
+ else
155
+ storage_key = StorageHelper.encode_storage_key(pallet_name, item_name, key, registry)._to_hex
156
+ data = state_getStorage(storage_key, block_hash)
157
+ StorageHelper.decode_storage(data, value[:type], value[:modifier] == 'Optional', value[:fallback], registry)
158
+ end
159
+ end
160
+
161
+ def get_storage2(block_hash, pallet_name, item_name, value_of_key, metadata)
162
+ raise 'Metadata should not be nil' if metadata.nil?
163
+
164
+ registry = Metadata.build_registry(metadata)
165
+ item = Metadata.get_storage_item(
166
+ pallet_name, item_name, metadata
167
+ )
168
+ raise "No such storage item: `#{pallet_name}`.`#{item_name}`" if item.nil?
169
+
170
+ modifier = item._get(:modifier) # Default | Optional
171
+ fallback = item._get(:fallback)
172
+ type = item._get(:type)
173
+
174
+ plain = type._get(:plain)
175
+ map = type._get(:map)
176
+ # debug
177
+
178
+ key, value =
179
+ if plain
180
+ [
181
+ nil,
182
+ { type: plain,
183
+ modifier: modifier, fallback: fallback }
184
+ ]
185
+ elsif map
186
+ [
187
+ { value: value_of_key,
188
+ type: map._get(:key), hashers: map._get(:hashers) },
189
+ { type: map._get(:value),
190
+ modifier: modifier, fallback: fallback }
191
+ ]
192
+ else
193
+ raise 'NoSuchStorageType'
194
+ end
195
+ get_storage1(block_hash, pallet_name, item_name, key, value, registry)
196
+ end
197
+
198
+ def to_pascal(str)
199
+ str.split('_').collect(&:capitalize).join
200
+ end
201
+
202
+ # convert key to byte array
203
+ def c(key)
204
+ if key.start_with?('0x')
205
+ key._to_bytes
206
+ elsif key.to_i.to_s == key # check if key is a number
207
+ key.to_i
208
+ else
209
+ key
210
+ end
211
+ end
212
+
213
+ end
214
+ end
@@ -3,65 +3,55 @@
3
3
  require 'uri'
4
4
  require 'net/http'
5
5
  require 'json'
6
- require_relative './rpc_request_builder'
7
- require_relative './http_client_metadata'
8
- require_relative './http_client_storage'
9
6
 
10
- # TODO: method_name = cmd.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
11
- module ScaleRb
12
- module HttpClient
13
- extend RpcRequestBuilder
7
+ require_relative 'client_ext'
14
8
 
15
- class << self
16
- def request(url, body)
17
- ScaleRb.logger.debug "url: #{url}"
18
- ScaleRb.logger.debug "body: #{body}"
19
- uri = URI(url)
20
- req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json')
21
- req.body = body
22
- http = Net::HTTP.new(uri.host, uri.port)
23
- http.use_ssl = true if uri.instance_of? URI::HTTPS
24
- res = http.request(req)
9
+ module ScaleRb
10
+ class HttpClient
11
+ include ClientExt
12
+ attr_accessor :supported_methods
25
13
 
26
- raise res.class.name unless res.is_a?(Net::HTTPSuccess)
14
+ def initialize(url)
15
+ # check if the url is started with http or https
16
+ url_regex = %r{^https?://}
17
+ raise 'url format is not correct' unless url.match?(url_regex)
27
18
 
28
- result = JSON.parse(res.body)
29
- ScaleRb.logger.debug result
30
- raise result['error']['message'] if result['error']
19
+ @uri = URI.parse(url)
20
+ @supported_methods = request('rpc_methods', [])['methods']
21
+ end
31
22
 
32
- result['result']
33
- rescue StandardError => e
34
- ScaleRb.logger.error e.message
35
- ScaleRb.logger.error 'retry...'
36
- sleep 2
37
- request(url, body)
23
+ def request(method, params = [])
24
+ # don't check for rpc_methods, because there is no @supported_methods when initializing
25
+ if method != 'rpc_methods' && !@supported_methods.include?(method)
26
+ raise "Method `#{method}` is not supported. It should be in [#{@supported_methods.join(', ')}]."
38
27
  end
39
28
 
40
- def json_rpc_call(url, method, *params)
41
- body = build_json_rpc_body(method, params, Time.now.to_i)
42
- request(url, body)
43
- end
29
+ http = Net::HTTP.new(@uri.host, @uri.port)
30
+ http.use_ssl = @uri.scheme == 'https'
44
31
 
45
- def respond_to_missing?(*_args)
46
- true
47
- end
32
+ request = Net::HTTP::Post.new(@uri, 'Content-Type' => 'application/json')
33
+ request.body = { jsonrpc: '2.0', method: method, params: params, id: Time.now.to_i }.to_json
34
+ ScaleRb.logger.debug "Request: #{request.body}"
48
35
 
49
- def method_missing(method, *args)
50
- ScaleRb.logger.debug "#{method}(#{args.join(', ')})"
51
- # check if the first argument is a url
52
- url_regex = %r{^https?://}
53
- raise 'url format is not correct' unless args[0].match?(url_regex)
36
+ # https://docs.ruby-lang.org/en/master/Net/HTTPResponse.html
37
+ response = http.request(request)
38
+ raise response unless response.is_a?(Net::HTTPOK)
54
39
 
55
- url = args[0]
56
- raise NoMethodError, "undefined rpc method `#{method}'" unless rpc_methods(url).include?(method.to_s)
40
+ body = JSON.parse(response.body)
41
+ ScaleRb.logger.debug "Response: #{body}"
42
+ raise body['error'] if body['error']
57
43
 
58
- json_rpc_call(url, method, *args[1..])
59
- end
44
+ body['result']
45
+ end
60
46
 
61
- def rpc_methods(url)
62
- result = json_rpc_call(url, 'rpc_methods', [])
63
- result['methods']
64
- end
47
+ def respond_to_missing?(*_args)
48
+ true
49
+ end
50
+
51
+ def method_missing(method, *args)
52
+ ScaleRb.logger.debug "#{method}(#{args.join(', ')})"
53
+
54
+ request(method.to_s, args)
65
55
  end
66
56
  end
67
- end
57
+ end