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 +4 -4
- data/Dockerfile +17 -0
- data/Gemfile.lock +5 -6
- data/README.md +240 -9
- data/exe/metadata +6 -6
- data/lib/helper.rb +129 -0
- data/lib/substrate_client.rb +274 -153
- data/lib/substrate_client/version.rb +1 -1
- data/lib/substrate_client_sync.rb +190 -0
- data/lib/timeout_queue.rb +34 -0
- data/lib/websocket.rb +79 -0
- data/substrate_client.gemspec +1 -2
- metadata +9 -20
- data/exe/substrate_client +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: af04674d5e75dbafa82eade829b5798fe87e015a51a8edb63d7845cdd38c27b9
|
4
|
+
data.tar.gz: 8afa612ea099169396f4ef3155c5158ba188b09ff602b5d515fcf6deadfe5739
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f5c5379451ece56ac1a868092446f66929c83eafb36dace7007b2f479616c3bb2c7a9aff2f77fd1ef25b0bdf5ebb0a431b17308d892ecc4ad3cad54a03700a2e
|
7
|
+
data.tar.gz: e179bd7fb5a421f1de150463d09a04ad369a15eb1f1027c5c21d9fdc837dda5ebed1671cac093e2a584b9c356ad3112881b990d0763394d533ddb7d3e22c7f00
|
data/Dockerfile
ADDED
@@ -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"]
|
data/Gemfile.lock
CHANGED
@@ -1,17 +1,16 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
substrate_client.rb (0.1.
|
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.
|
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.
|
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.
|
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.
|
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
|
-
###
|
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
|
-
|
31
|
+
client.method_list do |methods|
|
32
|
+
p methods
|
33
|
+
end
|
30
34
|
```
|
31
|
-
The rpc
|
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
|
-
|
262
|
+
5. Or, SubstrateClientSync:
|
34
263
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
|
data/exe/metadata
CHANGED
@@ -3,10 +3,10 @@
|
|
3
3
|
require "substrate_client"
|
4
4
|
require "json"
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
block_hash = ARGV[1]
|
6
|
+
url = ARGV[0] || "wss://kusama-rpc.polkadot.io/"
|
7
|
+
client = SubstrateClientSync.new(url)
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
|
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)
|
data/lib/helper.rb
ADDED
@@ -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
|