scale_rb 0.2.2 → 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: 204c629856c37264a38451c2655c36ea33aee9d97cb4220ab2d107dc692f1d79
4
- data.tar.gz: 4e16a199f0d77c5a7fa4150e05a00204f3244cb33d64ec79604f7e19ff43fbc3
3
+ metadata.gz: 79d55aeb610c1a4a11410703445983aa6b02cbf8ee21653a38d425b06c41e3dc
4
+ data.tar.gz: 8e7832aa2495fed7226797bd21bc23db257754c8d5160cba57f7830879fe592f
5
5
  SHA512:
6
- metadata.gz: 5de7ac76c8a4113b2ae965845b2d7a940eabce3b732fad370a6bd1be039f83c0f251525c35ebace750493c07d4372868bc82fefcbd215f19c30b8702cf2ab281
7
- data.tar.gz: a5796129a92d7bfca3d00cbdccf56a7574e3486d9562ef088ef74b5a24135cdbe6a3ee014692df2ba1bfbc3140dcd97a5d96bf575d73310eacabbde3e8876229
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.2)
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,95 +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
7
+ require_relative 'client_ext'
8
+
11
9
  module ScaleRb
12
- module HttpClient
13
- extend RpcRequestBuilder
10
+ class HttpClient
11
+ include ClientExt
12
+ attr_accessor :supported_methods
14
13
 
15
- class << self
16
- def request(url, body, tries = 0)
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)
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)
25
18
 
26
- raise res.class.name unless res.is_a?(Net::HTTPSuccess)
19
+ @uri = URI.parse(url)
20
+ @supported_methods = request('rpc_methods', [])['methods']
21
+ end
27
22
 
28
- result = JSON.parse(res.body)
29
- ScaleRb.logger.debug result
30
- raise result['error']['message'] if result['error']
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(', ')}]."
27
+ end
31
28
 
32
- result['result']
33
- rescue StandardError => e
34
- raise e unless tries < 5
29
+ http = Net::HTTP.new(@uri.host, @uri.port)
30
+ http.use_ssl = @uri.scheme == 'https'
35
31
 
36
- ScaleRb.logger.error e.message
37
- ScaleRb.logger.error 'retry after 5 seconds...'
38
- sleep 5
39
- request(url, body, tries + 1)
40
- 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}"
41
35
 
42
- def json_rpc_call(url, method, *params)
43
- body = build_json_rpc_body(method, params, Time.now.to_i)
44
- request(url, body)
45
- end
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)
46
39
 
47
- def respond_to_missing?(*_args)
48
- true
49
- end
40
+ body = JSON.parse(response.body)
41
+ ScaleRb.logger.debug "Response: #{body}"
42
+ raise body['error'] if body['error']
50
43
 
51
- def method_missing(method, *args)
52
- ScaleRb.logger.debug "#{method}(#{args.join(', ')})"
53
- # check if the first argument is a url
54
- url_regex = %r{^https?://}
55
- raise 'url format is not correct' unless args[0].match?(url_regex)
44
+ body['result']
45
+ end
56
46
 
57
- url = args[0]
58
- raise NoMethodError, "undefined rpc method `#{method}'" unless rpc_methods(url).include?(method.to_s)
47
+ def respond_to_missing?(*_args)
48
+ true
49
+ end
59
50
 
60
- json_rpc_call(url, method, *args[1..])
61
- end
51
+ def method_missing(method, *args)
52
+ ScaleRb.logger.debug "#{method}(#{args.join(', ')})"
62
53
 
63
- def rpc_methods(url)
64
- result = json_rpc_call(url, 'rpc_methods', [])
65
- result['methods']
66
- end
54
+ request(method.to_s, args)
67
55
  end
68
56
  end
69
- end
70
-
71
- # https://polkadot.js.org/docs/substrate/rpc/
72
- #
73
- # Examples:
74
- # # Get all supported rpc methods
75
- # ScaleRb::HttpClient.rpc_methods("https://rpc.darwinia.network")
76
- #
77
- # # eth_blockNumber
78
- # ScaleRb::HttpClient.eth_blockNumber("https://rpc.darwinia.network")
79
- #
80
- # # system_name
81
- # ScaleRb::HttpClient.system_name("https://rpc.darwinia.network")
82
- #
83
- # # chain_getHead
84
- # ScaleRb::HttpClient.chain_getHead("https://rpc.darwinia.network")
85
- #
86
- # # state_getMetadata of darwinia block #1582
87
- # ScaleRb::HttpClient.state_getMetadata(
88
- # "https://rpc.darwinia.network",
89
- # "0xb5a4f16d0feba7531e75315432b4d31a5b918987e026437890a2cbf5b8d9956d"
90
- # )
91
- #
92
- # # eth_getBalance of address 0x0000000000000000000000000000000000000000 at block #1582
93
- # ScaleRb::HttpClient.eth_getBalance(
94
- # "https://rpc.darwinia.network",
95
- # "0x0000000000000000000000000000000000000000",
96
- # 1582
97
- # )
57
+ end