substrate_client.rb 0.1.2 → 0.1.7

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: a1cec3e9d753a9d61bace4bcdcde8aba48148fe3b278453c2402272752043739
4
- data.tar.gz: ec5b2e682cb056ac2d172798b4a32de06755ae22dcc06aec8c691141f684d128
3
+ metadata.gz: af04674d5e75dbafa82eade829b5798fe87e015a51a8edb63d7845cdd38c27b9
4
+ data.tar.gz: 8afa612ea099169396f4ef3155c5158ba188b09ff602b5d515fcf6deadfe5739
5
5
  SHA512:
6
- metadata.gz: e5476a44ef876d98d9ebbcbcaa46fcb3072b86667b0d4bc90a808bd1415fafd2b85cea74792366ca599ae521660898d19de21ad2856324c2b185707c638dbd26
7
- data.tar.gz: bda69cc1f2f0bf6f9f12535550c266f295e502bb55c281c54d9f85296df5b7bc57b330ce24d1ad480cba342ab3916480b1ce72d8b734ebaaee23bcab5a466fec
6
+ metadata.gz: f5c5379451ece56ac1a868092446f66929c83eafb36dace7007b2f479616c3bb2c7a9aff2f77fd1ef25b0bdf5ebb0a431b17308d892ecc4ad3cad54a03700a2e
7
+ data.tar.gz: e179bd7fb5a421f1de150463d09a04ad369a15eb1f1027c5c21d9fdc837dda5ebed1671cac093e2a584b9c356ad3112881b990d0763394d533ddb7d3e22c7f00
@@ -0,0 +1,17 @@
1
+ FROM ruby:2.6-alpine3.11
2
+
3
+ ENV BUILD_PACKAGES curl-dev build-base
4
+
5
+ RUN apk update && \
6
+ apk upgrade && \
7
+ apk add git curl $BUILD_PACKAGES
8
+
9
+ WORKDIR /usr/src/app
10
+
11
+ COPY . .
12
+
13
+ RUN gem install bundler:1.17.3 && \
14
+ bundle install && \
15
+ rake install:local
16
+
17
+ CMD ["sh"]
@@ -1,17 +1,16 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- substrate_client.rb (0.1.2)
4
+ substrate_client.rb (0.1.7)
5
5
  activesupport (~> 5.2.4)
6
6
  eventmachine (~> 1.2.7)
7
7
  faye-websocket (~> 0.10.9)
8
- scale.rb (~> 0.2.1)
9
- substrate_common.rb (~> 0.1.8)
8
+ scale.rb (~> 0.2.5)
10
9
 
11
10
  GEM
12
11
  remote: https://rubygems.org/
13
12
  specs:
14
- activesupport (5.2.4.2)
13
+ activesupport (5.2.4.3)
15
14
  concurrent-ruby (~> 1.0, >= 1.0.2)
16
15
  i18n (>= 0.7, < 2)
17
16
  minitest (~> 5.1)
@@ -29,7 +28,7 @@ GEM
29
28
  concurrent-ruby (~> 1.0)
30
29
  json (2.3.0)
31
30
  method_source (0.9.2)
32
- minitest (5.14.0)
31
+ minitest (5.14.1)
33
32
  pry (0.12.2)
34
33
  coderay (~> 1.1.0)
35
34
  method_source (~> 0.9.0)
@@ -47,7 +46,7 @@ GEM
47
46
  diff-lcs (>= 1.2.0, < 2.0)
48
47
  rspec-support (~> 3.9.0)
49
48
  rspec-support (3.9.2)
50
- scale.rb (0.2.1)
49
+ scale.rb (0.2.5)
51
50
  activesupport (>= 4.0.0)
52
51
  json (~> 2.3.0)
53
52
  substrate_common.rb (~> 0.1.8)
data/README.md CHANGED
@@ -20,23 +20,254 @@ Or install it yourself as:
20
20
 
21
21
  ## Usage
22
22
 
23
- ### Api list
23
+ ### Supported rpc methods
24
+
25
+ #### rpc method list
24
26
 
25
27
  ```ruby
26
28
  require "substrate_client"
27
29
 
28
30
  client = SubstrateClient.new("wss://kusama-rpc.polkadot.io/")
29
- puts client.method_list
31
+ client.method_list do |methods|
32
+ p methods
33
+ end
30
34
  ```
31
- The rpc api methods is dynamically generated, so the methods returned by this method can be called.
35
+ The rpc methods can be dynamically called by its name, so the methods returned by this method can all be used.
36
+
37
+
38
+
39
+ #### rpc methods
40
+
41
+ But, in order to show the parameters more clearly, some important or frequently used methods are hard-coded:
42
+
43
+ - chain_get_finalised_head(&callback)
44
+
45
+ Get hash of the last finalized block in the canon chain
46
+
47
+ ```ruby
48
+ client.chain_get_finalised_head do |head|
49
+ p head
50
+ end
51
+ ```
52
+
53
+
54
+
55
+ - chain_get_head(&callback)
56
+
57
+ Retrieves the header
58
+
59
+
60
+
61
+ - chain_get_header(block_hash = nil, &callback)
62
+
63
+ Retrieves the header for a specific block
64
+
65
+
66
+
67
+ - chain_get_block(block_hash = nil, &callback)
68
+
69
+ Get header and body of a relay chain block
70
+
71
+
72
+
73
+ - chain_get_block_hash(block_id, &callback)
74
+
75
+ Get the block hash for a specific block
76
+
77
+
78
+
79
+ - chain_get_runtime_version(block_hash = nil, &callback)
80
+
81
+ Get the runtime version for a specific block
82
+
83
+
84
+
85
+ - state_get_metadata(block_hash = nil, &callback)
86
+
87
+ Returns the runtime metadata by block
88
+
89
+
90
+
91
+ - state_get_storage(storage_key, block_hash = nil, &callback)
92
+
93
+ Retrieves the storage for a key
94
+
95
+
96
+
97
+ - system_name(&callback)
98
+
99
+
100
+
101
+ - system_version(&callback)
102
+
103
+
104
+
105
+ - chain_subscribe_all_heads(&callback)
106
+
107
+ Retrieves the newest header via subscription. This will return data continuously until you unsubscribe the subscription.
108
+
109
+ ```ruby
110
+ subscription = client.chain_subscribe_all_heads do |data|
111
+ p data
112
+ end
113
+ ```
114
+
115
+ - chain_unsubscribe_all_heads(subscription)
116
+
117
+ Unsubscribe newest header subscription.
118
+
119
+ ```ruby
120
+ client.chain_unsubscribe_all_heads(subscription)
121
+ ```
122
+
123
+
124
+
125
+ - chain_subscribe_new_heads(&callback)
126
+
127
+ Retrieves the best header via subscription. This will return data continuously until you unsubscribe the subscription.
128
+
129
+ - chain_unsubscribe_new_heads(subscription)
130
+
131
+ Unsubscribe the best header subscription.
132
+
133
+
134
+
135
+
136
+ - chain_subscribe_finalized_heads(&callback)
137
+
138
+ Retrieves the best finalized header via subscription. This will return data continuously until you unsubscribe the subscription.
139
+
140
+ - chain_unsubscribe_finalized_heads(subscription)
141
+
142
+ Unsubscribe the best finalized header subscription.
143
+
144
+
145
+
146
+ - state_subscribe_runtime_version(&callback)
147
+
148
+ Retrieves the runtime version via subscription.
149
+
150
+ - state_unsubscribe_runtime_version(subscription)
151
+
152
+ Unsubscribe the runtime version subscription.
153
+
154
+
155
+
156
+ - state_subscribe_storage(keys, &callback)
157
+
158
+ Subscribes to storage changes for the provided keys until unsubscribe.
159
+
160
+ ```ruby
161
+ subscription = client.state_subscribe_storage ["0x26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7"] do |data|
162
+ p data
163
+ end
164
+ ```
165
+
166
+ - state_unsubscribe_storage(subscription)
167
+
168
+ Unsubscribe storage changes.
169
+
170
+
171
+ ### Cutom methods based on rpc methods
172
+
173
+ These methods will encode the parameters and decode the returned data
174
+
175
+ - get_block_number(block_hash, &callback)
176
+
177
+ - get_metadata(block_hash, &callback)
178
+
179
+ - get_block(block_hash=nil, &callback)
180
+
181
+ - get_block_events(block_hash, &callback)
182
+
183
+ - subscribe_block_events(&callback, &callback)
184
+
185
+ - get_storage(module_name, storage_name, params = nil, block_hash = nil, &callback)
186
+
187
+ ```ruby
188
+ client.get_storage("Balances", "TotalIssuance", nil, nil) do |storage|
189
+ p storage
190
+ end
191
+
192
+ client.get_storage("System", "Account", ["0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"], nil) do |storage|
193
+ p storage
194
+ end
195
+
196
+ client.get_storage("ImOnline", "AuthoredBlocks", [2818, "0x749ddc93a65dfec3af27cc7478212cb7d4b0c0357fef35a0163966ab5333b757"], nil) do |storage|
197
+ p storage
198
+ end
199
+ ```
200
+
201
+ - compose_call(module_name, call_name, params, block_hash=nil, &callback)
202
+
203
+ ```ruby
204
+ compose_call "Balances", "Transfer", { dest: "0x586cb27c291c813ce74e86a60dad270609abf2fc8bee107e44a80ac00225c409", value: 1_000_000_000_000 }, nil do |hex|
205
+ p hex
206
+ end
207
+ ```
208
+
209
+
210
+
211
+ ## Synchronized client
212
+
213
+ There is also a synchronized version of the client, which is disconnected every time the data is retrieved. It is very convenient in some situations. But this version cannot use the subscription interface.
214
+
215
+ ```ruby
216
+ client = SubstrateClientSync.new "wss://kusama-rpc.polkadot.io/"
217
+ p client.method_list
218
+ p client.chain_get_head
219
+ p client.chain_get_finalised_head
220
+ p client.chain_get_header(block_hash = nil)
221
+ ...
222
+ ```
223
+
224
+ ## Docker
225
+
226
+ 1. update to latest image
227
+
228
+ `docker pull itering/substrate_client:latest`
229
+
230
+ 2. Run image:
231
+
232
+ `docker run -it itering/substrate_client:latest`
233
+
234
+ This will enter the container with a linux shell opened.
235
+
236
+ ```shell
237
+ /usr/src/app #
238
+ ```
239
+
240
+ 3. Type `rspec` to run all tests
241
+
242
+ ```shell
243
+ /usr/src/app # rspec
244
+ ...................
245
+
246
+ Finished in 0.00883 seconds (files took 0.09656 seconds to load)
247
+ 5 examples, 0 failures
248
+ ```
249
+
250
+ 4. Or, type `./bin/console` to enter the ruby interactive environment and run any decode or encode code
251
+
252
+ ```shell
253
+ /usr/src/app # ./bin/console
254
+ [1] pry(main)> client = SubstrateClient.new("wss://kusama-rpc.polkadot.io/")
255
+ => #<SubstrateClient:0x000055a78f124f58 ...
256
+ [2] pry(main)> client.method_list do |methods| p methods end
257
+ => ...
258
+ [3] pry(main)> subscription = client.chain_subscribe_new_heads do |data| p data end
259
+ ...
260
+ ```
32
261
 
33
- ## TODO
262
+ 5. Or, SubstrateClientSync:
34
263
 
35
- - [x] ws wss request support
36
- - [ ] http request support
37
- - [x] generate storage key
38
- - [x] call any api supported by substrate node with ruby's method missing function
39
- - [ ] metadata caching
264
+ ```shell
265
+ /usr/src/app # ./bin/console
266
+ [1] pry(main)> client = SubstrateClientSync.new("wss://kusama-rpc.polkadot.io/", spec_name: "kusama")
267
+ => #<SubstrateClientSync:0x000055a78edfd6e0 @request_id=1, @spec_name="kusama", @url="wss://kusama-rpc.polkadot.io/">
268
+ [2] pry(main)> client.chain_get_head
269
+ => "0xb3c3a220d4639b7c62f179f534b3a66336a115ebc18f13db053f0c57437c45fc"
270
+ ```
40
271
 
41
272
  ## Development
42
273
 
@@ -3,10 +3,10 @@
3
3
  require "substrate_client"
4
4
  require "json"
5
5
 
6
- # metadata url block_hash
7
- url = ARGV[0] || "wss://cc3-5.kusama.network/"
8
- block_hash = ARGV[1]
6
+ url = ARGV[0] || "wss://kusama-rpc.polkadot.io/"
7
+ client = SubstrateClientSync.new(url)
9
8
 
10
- client = SubstrateClient.new(url)
11
- client.init(block_hash)
12
- puts JSON.pretty_generate(client.metadata.value.value)
9
+ block_hash = ARGV[1] || client.chain_get_finalised_head
10
+
11
+ metadata = client.get_metadata(block_hash)
12
+ puts JSON.pretty_generate(metadata.value.to_human)
@@ -0,0 +1,129 @@
1
+
2
+ class SubstrateClient::Helper
3
+ class << self
4
+ def generate_storage_hash_from_metadata(metadata, module_name, storage_name, params = nil)
5
+ # find the storage item from metadata
6
+ metadata_modules = metadata.value.value[:metadata][:modules]
7
+ metadata_module = metadata_modules.detect { |mm| mm[:name] == module_name }
8
+ raise "Module '#{module_name}' not exist" unless metadata_module
9
+ storage_item = metadata_module[:storage][:items].detect { |item| item[:name] == storage_name }
10
+ raise "Storage item '#{storage_name}' not exist. \n#{metadata_module.inspect}" unless storage_item
11
+
12
+ if storage_item[:type][:Plain]
13
+ return_type = storage_item[:type][:Plain]
14
+ elsif map = storage_item[:type][:Map]
15
+ raise "Storage call of type \"Map\" requires 1 parameter" if params.nil? || params.length != 1
16
+
17
+ hasher = map[:hasher]
18
+ return_type = map[:value]
19
+ # TODO: decode to account id if param is address
20
+ # params[0] = decode(params[0]) if map[:key] == "AccountId"
21
+ params[0] = Scale::Types.get(map[:key]).new(params[0]).encode
22
+ elsif map = storage_item[:type][:DoubleMap]
23
+ raise "Storage call of type \"DoubleMapType\" requires 2 parameters" if params.nil? || params.length != 2
24
+
25
+ hasher = map[:hasher]
26
+ hasher2 = map[:key2Hasher]
27
+ return_type = map[:value]
28
+ params[0] = Scale::Types.get(map[:key1]).new(params[0]).encode
29
+ params[1] = Scale::Types.get(map[:key2]).new(params[1]).encode
30
+ else
31
+ raise NotImplementedError
32
+ end
33
+
34
+ storage_hash = generate_storage_hash(
35
+ module_name,
36
+ storage_name,
37
+ params,
38
+ hasher,
39
+ hasher2,
40
+ metadata.value.value[:metadata][:version]
41
+ )
42
+ [storage_hash, return_type]
43
+ end
44
+
45
+ def generate_storage_hash(module_name, storage_name, params = nil, hasher = nil, hasher2 = nil, metadata_version = nil)
46
+ if metadata_version and metadata_version >= 9
47
+ storage_hash = Crypto.twox128(module_name) + Crypto.twox128(storage_name)
48
+
49
+ params&.each_with_index do |param, index|
50
+ if index == 0
51
+ param_hasher = hasher
52
+ elsif index == 1
53
+ param_hasher = hasher2
54
+ else
55
+ raise "Unexpected third parameter for storage call"
56
+ end
57
+
58
+ param_key = param.hex_to_bytes
59
+ param_hasher = "Twox128" if param_hasher.nil?
60
+ storage_hash += Crypto.send param_hasher.underscore, param_key
61
+ end
62
+
63
+ "0x#{storage_hash}"
64
+ else
65
+ # TODO: add test
66
+ storage_hash = module_name + " " + storage_name
67
+
68
+ unless params.nil?
69
+ params = [params] if params.class != ::Array
70
+ params_key = params.join("")
71
+ hasher = "Twox128" if hasher.nil?
72
+ storage_hash += params_key.hex_to_bytes.bytes_to_utf8
73
+ end
74
+
75
+ "0x#{Crypto.send( hasher.underscore, storage_hash )}"
76
+ end
77
+ end
78
+
79
+ def compose_call_from_metadata(metadata, module_name, call_name, params)
80
+ call = metadata.get_module_call(module_name, call_name)
81
+
82
+ value = {
83
+ call_index: call[:lookup],
84
+ module_name: module_name,
85
+ call_name: call_name,
86
+ params: []
87
+ }
88
+
89
+ params.keys.each_with_index do |call_param_name, i|
90
+ param_value = params[call_param_name]
91
+ value[:params] << {
92
+ name: call_param_name.to_s,
93
+ type: call[:args][i][:type],
94
+ value: param_value
95
+ }
96
+ end
97
+
98
+ Scale::Types::Extrinsic.new(value).encode
99
+ end
100
+
101
+ def decode_block(block)
102
+ block["block"]["header"]["number"] = block["block"]["header"]["number"].to_i(16)
103
+
104
+ block["block"]["extrinsics"].each_with_index do |hex, i|
105
+ scale_bytes = Scale::Bytes.new(hex)
106
+ block["block"]["extrinsics"][i] = Scale::Types::Extrinsic.decode(scale_bytes).to_human
107
+ end
108
+
109
+ block['block']['header']["digest"]["logs"].each_with_index do |hex, i|
110
+ scale_bytes = Scale::Bytes.new(hex)
111
+ block['block']['header']["digest"]["logs"][i] = Scale::Types::LogDigest.decode(scale_bytes).to_human
112
+ end
113
+
114
+ block
115
+ end
116
+
117
+ # chain_unsubscribe_runtime_version
118
+ # =>
119
+ # chain_unsubscribeRuntimeVersion
120
+ def real_method_name(method_name)
121
+ segments = method_name.to_s.split("_")
122
+ if segments.length == 1
123
+ segments[0]
124
+ else
125
+ segments[0] + "_" + segments[1] + segments[2..].map(&:capitalize).join
126
+ end
127
+ end
128
+ end
129
+ end