scale_rb 0.2.2 → 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: 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