substrate_client.rb 0.1.2 → 0.1.7

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: 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