scale_rb 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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