substrate_client.rb 0.1.3 → 0.1.8
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 +16 -19
- data/README.md +142 -9
- data/exe/metadata +6 -6
- data/lib/helper.rb +119 -0
- data/lib/substrate_client.rb +92 -145
- data/lib/substrate_client/version.rb +1 -1
- data/substrate_client.gemspec +3 -4
- metadata +16 -30
- 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: bb233970cdc1b51057d436d0ffc2ae411b99ce3c2e5c51ba3dfb5dcdae0fc960
|
4
|
+
data.tar.gz: 35750c213d2fbf7ae223cdd7b9f7a8bc40c7c7b8fab6a09603a0604db6533562
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5731212fc4ad5791bb607f132e660b89f7ff652937029e986040aeee85335939d5a5edf9dd9a21a91f346c2b88711657586747a01727f35eae8318b9b944a62f
|
7
|
+
data.tar.gz: 69514b6b9b3eb085f7fefc672dff0b3a85cdfb64babfad28f6ab788f98ee950e2a2e9fcf2e89ab448b7e8c7137da20ba6ec0e68d375ece1b9c41e5f77d3d6964
|
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.8)
|
5
5
|
activesupport (~> 5.2.4)
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
substrate_common.rb (~> 0.1.8)
|
6
|
+
kontena-websocket-client (~> 0.1.1)
|
7
|
+
scale.rb (~> 0.2.11)
|
8
|
+
substrate_common.rb (~> 0.1.9)
|
10
9
|
|
11
10
|
GEM
|
12
11
|
remote: https://rubygems.org/
|
13
12
|
specs:
|
14
|
-
activesupport (5.2.4.
|
13
|
+
activesupport (5.2.4.4)
|
15
14
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
16
15
|
i18n (>= 0.7, < 2)
|
17
16
|
minitest (~> 5.1)
|
@@ -19,17 +18,15 @@ GEM
|
|
19
18
|
base58 (0.2.3)
|
20
19
|
blake2b (0.10.0)
|
21
20
|
coderay (1.1.2)
|
22
|
-
concurrent-ruby (1.1.
|
21
|
+
concurrent-ruby (1.1.7)
|
23
22
|
diff-lcs (1.3)
|
24
|
-
|
25
|
-
faye-websocket (0.10.9)
|
26
|
-
eventmachine (>= 0.12.0)
|
27
|
-
websocket-driver (>= 0.5.1)
|
28
|
-
i18n (1.8.2)
|
23
|
+
i18n (1.8.5)
|
29
24
|
concurrent-ruby (~> 1.0)
|
30
|
-
json (2.3.
|
25
|
+
json (2.3.1)
|
26
|
+
kontena-websocket-client (0.1.1)
|
27
|
+
websocket-driver (~> 0.6.5)
|
31
28
|
method_source (0.9.2)
|
32
|
-
minitest (5.14.
|
29
|
+
minitest (5.14.2)
|
33
30
|
pry (0.12.2)
|
34
31
|
coderay (~> 1.1.0)
|
35
32
|
method_source (~> 0.9.0)
|
@@ -47,12 +44,12 @@ GEM
|
|
47
44
|
diff-lcs (>= 1.2.0, < 2.0)
|
48
45
|
rspec-support (~> 3.9.0)
|
49
46
|
rspec-support (3.9.2)
|
50
|
-
scale.rb (0.2.
|
47
|
+
scale.rb (0.2.11)
|
51
48
|
activesupport (>= 4.0.0)
|
52
49
|
json (~> 2.3.0)
|
53
|
-
substrate_common.rb (~> 0.1.
|
50
|
+
substrate_common.rb (~> 0.1.9)
|
54
51
|
thor (~> 0.19.0)
|
55
|
-
substrate_common.rb (0.1.
|
52
|
+
substrate_common.rb (0.1.9)
|
56
53
|
base58
|
57
54
|
blake2b
|
58
55
|
xxhash
|
@@ -60,9 +57,9 @@ GEM
|
|
60
57
|
thread_safe (0.3.6)
|
61
58
|
tzinfo (1.2.7)
|
62
59
|
thread_safe (~> 0.1)
|
63
|
-
websocket-driver (0.
|
60
|
+
websocket-driver (0.6.5)
|
64
61
|
websocket-extensions (>= 0.1.0)
|
65
|
-
websocket-extensions (0.1.
|
62
|
+
websocket-extensions (0.1.5)
|
66
63
|
xxhash (0.4.0)
|
67
64
|
|
68
65
|
PLATFORMS
|
data/README.md
CHANGED
@@ -20,23 +20,156 @@ Or install it yourself as:
|
|
20
20
|
|
21
21
|
## Usage
|
22
22
|
|
23
|
-
###
|
23
|
+
### Supported rpc methods
|
24
24
|
|
25
25
|
```ruby
|
26
26
|
require "substrate_client"
|
27
27
|
|
28
28
|
client = SubstrateClient.new("wss://kusama-rpc.polkadot.io/")
|
29
|
-
|
29
|
+
client.methods
|
30
|
+
```
|
31
|
+
returns like:
|
32
|
+
```shell
|
33
|
+
[
|
34
|
+
"account_nextIndex",
|
35
|
+
"author_hasKey",
|
36
|
+
...
|
37
|
+
"chain_getBlock",
|
38
|
+
"chain_getBlockHash",
|
39
|
+
...
|
40
|
+
]
|
41
|
+
```
|
42
|
+
|
43
|
+
The rpc methods can be dynamically called by its name, so you can call it like:
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
client.chain_getBlockHash(1024)
|
30
47
|
```
|
31
|
-
The rpc api methods is dynamically generated, so the methods returned by this method can be called.
|
32
48
|
|
33
|
-
|
49
|
+
### Origin rpc methods
|
50
|
+
|
51
|
+
- `account_nextIndex`
|
52
|
+
* `author_hasKey`
|
53
|
+
* `author_hasSessionKeys`
|
54
|
+
* `author_insertKey`
|
55
|
+
* `author_pendingExtrinsics`
|
56
|
+
* `author_removeExtrinsic`
|
57
|
+
* `author_rotateKeys`
|
58
|
+
* `author_submitExtrinsic`
|
59
|
+
* `babe_epochAuthorship`
|
60
|
+
* `chain_getBlock(block_hash = nil)`
|
61
|
+
* `chain_getBlockHash(block_id)`
|
62
|
+
* `chain_getFinalisedHead`
|
63
|
+
* `chain_getFinalizedHead`
|
64
|
+
* `chain_getHead`
|
65
|
+
* `chain_getHeader(block_hash = nil)`
|
66
|
+
* `chain_getRuntimeVersion(block_hash = nil)`
|
67
|
+
* `childstate_getKeys`
|
68
|
+
* `childstate_getStorage`
|
69
|
+
* `childstate_getStorageHash`
|
70
|
+
* `childstate_getStorageSize`
|
71
|
+
* `grandpa_roundState`
|
72
|
+
* `offchain_localStorageGet`
|
73
|
+
* `offchain_localStorageSet`
|
74
|
+
* `payment_queryInfo`
|
75
|
+
* `state_call`
|
76
|
+
* `state_callAt`
|
77
|
+
* `state_getKeys`
|
78
|
+
* `state_getKeysPaged`
|
79
|
+
* `state_getKeysPagedAt`
|
80
|
+
* `state_getMetadata(block_hash = nil)`
|
81
|
+
* `state_getPairs`
|
82
|
+
* `state_getReadProof`
|
83
|
+
* `state_getRuntimeVersion`
|
84
|
+
* `state_getStorage(storage_key, block_hash = nil)`
|
85
|
+
* `state_getStorageAt`
|
86
|
+
* `state_getStorageHash`
|
87
|
+
* `state_getStorageHashAt`
|
88
|
+
* `state_getStorageSize`
|
89
|
+
* `state_getStorageSizeAt`
|
90
|
+
* `state_queryStorage`
|
91
|
+
* `state_queryStorageAt`
|
92
|
+
* `system_accountNextIndex`
|
93
|
+
* `system_addReservedPeer`
|
94
|
+
* `system_chain`
|
95
|
+
* `system_chainType`
|
96
|
+
* `system_dryRun`
|
97
|
+
* `system_dryRunAt`
|
98
|
+
* `system_health`
|
99
|
+
* `system_localListenAddresses`
|
100
|
+
* `system_localPeerId`
|
101
|
+
* `system_name`
|
102
|
+
* `system_networkState`
|
103
|
+
* `system_nodeRoles`
|
104
|
+
* `system_peers`
|
105
|
+
* `system_properties`
|
106
|
+
* `system_removeReservedPeer`
|
107
|
+
- `system_version`
|
108
|
+
|
109
|
+
### Wrap methods
|
110
|
+
|
111
|
+
These methods will encode the parameters and decode the returned data
|
112
|
+
|
113
|
+
- `get_block_number(block_hash)`
|
114
|
+
|
115
|
+
- `get_metadata(block_hash)`
|
116
|
+
|
117
|
+
- `get_block(block_hash=nil)`
|
118
|
+
|
119
|
+
- `get_block_events(block_hash)`
|
120
|
+
|
121
|
+
- `get_storage(module_name, storage_name, params = nil, block_hash = nil)`
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
client.get_storage("Balances", "TotalIssuance", nil, nil)
|
125
|
+
client.get_storage("System", "Account", ["0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"], nil)
|
126
|
+
client.get_storage("ImOnline", "AuthoredBlocks", [2818, "0x749ddc93a65dfec3af27cc7478212cb7d4b0c0357fef35a0163966ab5333b757"], nil)
|
127
|
+
```
|
128
|
+
|
129
|
+
- `compose_call(module_name, call_name, params, block_hash=nil)`
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
client.compose_call "Balances", "Transfer", { dest: "0x586cb27c291c813ce74e86a60dad270609abf2fc8bee107e44a80ac00225c409", value: 1_000_000_000_000 }, nil
|
133
|
+
```
|
134
|
+
|
135
|
+
## Docker
|
136
|
+
|
137
|
+
1. update to latest image
|
138
|
+
|
139
|
+
`docker pull itering/substrate_client:latest`
|
140
|
+
|
141
|
+
2. Run image:
|
142
|
+
|
143
|
+
`docker run -it itering/substrate_client:latest`
|
144
|
+
|
145
|
+
This will enter the container with a linux shell opened.
|
146
|
+
|
147
|
+
```shell
|
148
|
+
/usr/src/app #
|
149
|
+
```
|
150
|
+
|
151
|
+
3. Type `rspec` to run all tests
|
152
|
+
|
153
|
+
```shell
|
154
|
+
/usr/src/app # rspec
|
155
|
+
...................
|
156
|
+
|
157
|
+
Finished in 0.00883 seconds (files took 0.09656 seconds to load)
|
158
|
+
5 examples, 0 failures
|
159
|
+
```
|
160
|
+
|
161
|
+
4. Or, type `./bin/console` to enter the ruby interactive environment and run any decode or encode code
|
162
|
+
|
163
|
+
```shell
|
164
|
+
/usr/src/app # ./bin/console
|
165
|
+
[1] pry(main)> client = SubstrateClient.new("wss://kusama-rpc.polkadot.io/")
|
166
|
+
=> #<SubstrateClient:0x000055a78f124f58 ...
|
167
|
+
[2] pry(main)> client.methods
|
168
|
+
=> ...
|
169
|
+
[3] pry(main)> client.chain_getHead
|
170
|
+
=> "0xb3c3a220d4639b7c62f179f534b3a66336a115ebc18f13db053f0c57437c45fc"
|
171
|
+
```
|
34
172
|
|
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
|
40
173
|
|
41
174
|
## Development
|
42
175
|
|
data/exe/metadata
CHANGED
@@ -3,10 +3,10 @@
|
|
3
3
|
require "substrate_client"
|
4
4
|
require "json"
|
5
5
|
|
6
|
-
|
7
|
-
url = ARGV[0] || "wss://cc3-5.kusama.network/"
|
8
|
-
block_hash = ARGV[1]
|
9
|
-
|
6
|
+
url = ARGV[0] || "wss://kusama-rpc.polkadot.io"
|
10
7
|
client = SubstrateClient.new(url)
|
11
|
-
|
12
|
-
|
8
|
+
|
9
|
+
block_hash = ARGV[1] || client.chain_getFinalisedHead
|
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,119 @@
|
|
1
|
+
|
2
|
+
class SubstrateClient::Helper
|
3
|
+
class << self
|
4
|
+
def generate_storage_key_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
|
+
type = Scale::Types.get(map[:key])
|
22
|
+
params[0] = type.new(params[0]).encode
|
23
|
+
elsif map = storage_item[:type][:DoubleMap]
|
24
|
+
raise "Storage call of type \"DoubleMapType\" requires 2 parameters" if params.nil? || params.length != 2
|
25
|
+
|
26
|
+
hasher = map[:hasher]
|
27
|
+
hasher2 = map[:key2Hasher]
|
28
|
+
return_type = map[:value]
|
29
|
+
params[0] = Scale::Types.get(map[:key1]).new(params[0]).encode
|
30
|
+
params[1] = Scale::Types.get(map[:key2]).new(params[1]).encode
|
31
|
+
else
|
32
|
+
raise NotImplementedError
|
33
|
+
end
|
34
|
+
|
35
|
+
storage_key = generate_storage_key(
|
36
|
+
module_name,
|
37
|
+
storage_name,
|
38
|
+
params,
|
39
|
+
hasher,
|
40
|
+
hasher2,
|
41
|
+
metadata.value.value[:metadata][:version]
|
42
|
+
)
|
43
|
+
[storage_key, return_type]
|
44
|
+
end
|
45
|
+
|
46
|
+
def generate_storage_key(module_name, storage_name, params = nil, hasher = nil, hasher2 = nil, metadata_version = nil)
|
47
|
+
if metadata_version and metadata_version >= 9
|
48
|
+
storage_key = Crypto.twox128(module_name) + Crypto.twox128(storage_name)
|
49
|
+
|
50
|
+
params&.each_with_index do |param, index|
|
51
|
+
if index == 0
|
52
|
+
param_hasher = hasher
|
53
|
+
elsif index == 1
|
54
|
+
param_hasher = hasher2
|
55
|
+
else
|
56
|
+
raise "Unexpected third parameter for storage call"
|
57
|
+
end
|
58
|
+
|
59
|
+
param_key = param.hex_to_bytes
|
60
|
+
param_hasher = "Twox128" if param_hasher.nil?
|
61
|
+
storage_key += Crypto.send param_hasher.underscore, param_key
|
62
|
+
end
|
63
|
+
|
64
|
+
"0x#{storage_key}"
|
65
|
+
else
|
66
|
+
# TODO: add test
|
67
|
+
storage_key = module_name + " " + storage_name
|
68
|
+
|
69
|
+
unless params.nil?
|
70
|
+
params = [params] if params.class != ::Array
|
71
|
+
params_key = params.join("")
|
72
|
+
hasher = "Twox128" if hasher.nil?
|
73
|
+
storage_key += params_key.hex_to_bytes.bytes_to_utf8
|
74
|
+
end
|
75
|
+
|
76
|
+
"0x#{Crypto.send( hasher.underscore, storage_key )}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def compose_call_from_metadata(metadata, module_name, call_name, params)
|
81
|
+
call = metadata.get_module_call(module_name, call_name)
|
82
|
+
|
83
|
+
value = {
|
84
|
+
call_index: call[:lookup],
|
85
|
+
module_name: module_name,
|
86
|
+
call_name: call_name,
|
87
|
+
params: []
|
88
|
+
}
|
89
|
+
|
90
|
+
params.keys.each_with_index do |call_param_name, i|
|
91
|
+
param_value = params[call_param_name]
|
92
|
+
value[:params] << {
|
93
|
+
name: call_param_name.to_s,
|
94
|
+
type: call[:args][i][:type],
|
95
|
+
value: param_value
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
Scale::Types::Extrinsic.new(value).encode
|
100
|
+
end
|
101
|
+
|
102
|
+
def decode_block(block)
|
103
|
+
block["block"]["header"]["number"] = block["block"]["header"]["number"].to_i(16)
|
104
|
+
|
105
|
+
block["block"]["extrinsics"].each_with_index do |hex, i|
|
106
|
+
scale_bytes = Scale::Bytes.new(hex)
|
107
|
+
block["block"]["extrinsics"][i] = Scale::Types::Extrinsic.decode(scale_bytes).to_human
|
108
|
+
end
|
109
|
+
|
110
|
+
block['block']['header']["digest"]["logs"].each_with_index do |hex, i|
|
111
|
+
scale_bytes = Scale::Bytes.new(hex)
|
112
|
+
block['block']['header']["digest"]["logs"][i] = Scale::Types::LogDigest.decode(scale_bytes).to_human
|
113
|
+
end
|
114
|
+
|
115
|
+
block
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
data/lib/substrate_client.rb
CHANGED
@@ -1,52 +1,45 @@
|
|
1
1
|
require "substrate_client/version"
|
2
2
|
|
3
|
-
require "
|
4
|
-
require "scale"
|
5
|
-
|
6
|
-
require "faye/websocket"
|
7
|
-
require "eventmachine"
|
3
|
+
require "logger"
|
4
|
+
require "scale.rb"
|
8
5
|
require "json"
|
9
6
|
require "active_support"
|
10
7
|
require "active_support/core_ext/string"
|
8
|
+
require "helper"
|
9
|
+
require 'kontena-websocket-client'
|
11
10
|
|
12
11
|
def ws_request(url, payload)
|
13
12
|
result = nil
|
13
|
+
Kontena::Websocket::Client.connect(url, {}) do |client|
|
14
|
+
client.send(payload.to_json)
|
14
15
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
ws.on :open do |event|
|
19
|
-
# p [:open]
|
20
|
-
ws.send(payload.to_json)
|
21
|
-
end
|
22
|
-
|
23
|
-
ws.on :message do |event|
|
24
|
-
# p [:message, event.data]
|
25
|
-
if event.data.include?("jsonrpc")
|
26
|
-
result = JSON.parse event.data
|
27
|
-
ws.close(3001, "data received")
|
28
|
-
EM.stop
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
ws.on :close do |event|
|
33
|
-
# p [:close, event.code, event.reason]
|
34
|
-
ws = nil
|
16
|
+
client.read do |message|
|
17
|
+
result = JSON.parse message
|
18
|
+
client.close(1000)
|
35
19
|
end
|
36
20
|
end
|
37
21
|
|
38
|
-
result
|
22
|
+
return result
|
23
|
+
rescue Kontena::Websocket::CloseError => e
|
24
|
+
raise SubstrateClient::WebsocketError, e.reason
|
25
|
+
rescue Kontena::Websocket::Error => e
|
26
|
+
raise SubstrateClient::WebsocketError, e.reason
|
39
27
|
end
|
40
28
|
|
41
29
|
class SubstrateClient
|
30
|
+
class WebsocketError < StandardError; end
|
31
|
+
class RpcError < StandardError; end
|
32
|
+
class RpcTimeout < StandardError; end
|
33
|
+
|
42
34
|
attr_accessor :spec_name, :spec_version, :metadata
|
43
35
|
|
44
|
-
def initialize(url)
|
36
|
+
def initialize(url, spec_name: nil)
|
45
37
|
@url = url
|
46
38
|
@request_id = 1
|
39
|
+
@spec_name = spec_name
|
40
|
+
Scale::TypeRegistry.instance.load(spec_name: spec_name)
|
47
41
|
end
|
48
42
|
|
49
|
-
# TODO: error
|
50
43
|
def request(method, params)
|
51
44
|
payload = {
|
52
45
|
"jsonrpc" => "2.0",
|
@@ -54,145 +47,99 @@ class SubstrateClient
|
|
54
47
|
"params" => params,
|
55
48
|
"id" => @request_id
|
56
49
|
}
|
57
|
-
|
58
|
-
ws_request(@url, payload)
|
50
|
+
|
51
|
+
data = ws_request(@url, payload)
|
52
|
+
if data["error"]
|
53
|
+
raise RpcError, data["error"]
|
54
|
+
else
|
55
|
+
data["result"]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def init_runtime(block_hash=nil)
|
60
|
+
# set current runtime spec version
|
61
|
+
runtime_version = self.state_getRuntimeVersion(block_hash)
|
62
|
+
@spec_version = runtime_version["specVersion"]
|
63
|
+
Scale::TypeRegistry.instance.spec_version = @spec_version
|
64
|
+
|
65
|
+
# set current metadata
|
66
|
+
@metadata = self.get_metadata(block_hash)
|
67
|
+
Scale::TypeRegistry.instance.metadata = @metadata.value
|
68
|
+
true
|
69
|
+
end
|
70
|
+
|
71
|
+
def invoke(method, *params)
|
72
|
+
# params.reject! { |param| param.nil? }
|
73
|
+
request(method, params)
|
59
74
|
end
|
60
75
|
|
61
|
-
#
|
62
|
-
#
|
63
|
-
#
|
76
|
+
# ################################################
|
77
|
+
# origin rpc methods
|
78
|
+
# ################################################
|
64
79
|
def method_missing(method, *args)
|
65
|
-
|
66
|
-
data["result"]
|
80
|
+
invoke method, *args
|
67
81
|
end
|
68
82
|
|
69
83
|
# ################################################
|
70
|
-
# custom methods
|
84
|
+
# custom methods based on origin rpc methods
|
71
85
|
# ################################################
|
72
|
-
def
|
73
|
-
|
74
|
-
methods << "method_list"
|
75
|
-
methods << "get_storage_at"
|
86
|
+
def methods
|
87
|
+
invoke("rpc_methods")["methods"]
|
76
88
|
end
|
77
89
|
|
78
|
-
def
|
79
|
-
|
80
|
-
|
81
|
-
|
90
|
+
def get_block_number(block_hash)
|
91
|
+
header = self.chain_getHeader(block_hash)
|
92
|
+
header["number"].to_i(16)
|
93
|
+
end
|
82
94
|
|
83
|
-
|
84
|
-
|
85
|
-
|
95
|
+
def get_metadata(block_hash=nil)
|
96
|
+
hex = self.state_getMetadata(block_hash)
|
97
|
+
Scale::Types::Metadata.decode(Scale::Bytes.new(hex))
|
86
98
|
end
|
87
99
|
|
88
|
-
def
|
89
|
-
|
90
|
-
|
91
|
-
|
100
|
+
def get_block(block_hash=nil)
|
101
|
+
self.init_runtime(block_hash)
|
102
|
+
block = self.chain_getBlock(block_hash)
|
103
|
+
SubstrateClient::Helper.decode_block(block)
|
92
104
|
end
|
93
105
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
# TODO: uninit raise a exception
|
101
|
-
# find the storage item from metadata
|
102
|
-
metadata_modules = metadata[:modules]
|
103
|
-
metadata_module = metadata_modules.detect { |mm| mm[:name] == module_name }
|
104
|
-
raise "Module '#{module_name}' not exist" unless metadata_module
|
105
|
-
storage_item = metadata_module[:storage][:items].detect { |item| item[:name] == storage_function_name }
|
106
|
-
raise "Storage item '#{storage_function_name}' not exist. \n#{metadata_module.inspect}" unless storage_item
|
107
|
-
|
108
|
-
if storage_item[:type][:Plain]
|
109
|
-
return_type = storage_item[:type][:Plain]
|
110
|
-
elsif map = storage_item[:type][:Map]
|
111
|
-
raise "Storage call of type \"Map\" requires 1 parameter" if params.nil? || params.length != 1
|
112
|
-
|
113
|
-
hasher = map[:hasher]
|
114
|
-
return_type = map[:value]
|
115
|
-
# TODO: decode to account id if param is address
|
116
|
-
# params[0] = decode(params[0]) if map[:key] == "AccountId"
|
117
|
-
params[0] = Scale::Types.get(map[:key]).new(params[0]).encode
|
118
|
-
elsif map = storage_item[:type][:DoubleMap]
|
119
|
-
raise "Storage call of type \"DoubleMapType\" requires 2 parameters" if params.nil? || params.length != 2
|
120
|
-
|
121
|
-
hasher = map[:hasher]
|
122
|
-
hasher2 = map[:key2Hasher]
|
123
|
-
return_type = map[:value]
|
124
|
-
params[0] = Scale::Types.get(map[:key1]).new(params[0]).encode
|
125
|
-
params[1] = Scale::Types.get(map[:key2]).new(params[1]).encode
|
126
|
-
else
|
127
|
-
raise NotImplementedError
|
128
|
-
end
|
106
|
+
def get_block_events(block_hash=nil)
|
107
|
+
self.init_runtime(block_hash)
|
108
|
+
|
109
|
+
storage_key = "0x26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7"
|
110
|
+
events_data = state_getStorage storage_key, block_hash
|
129
111
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
112
|
+
scale_bytes = Scale::Bytes.new(events_data)
|
113
|
+
Scale::Types.get("Vec<EventRecord>").decode(scale_bytes).to_human
|
114
|
+
end
|
115
|
+
|
116
|
+
# Plain: client.get_storage("Sudo", "Key")
|
117
|
+
# Plain: client.get_storage("Balances", "TotalIssuance")
|
118
|
+
# Map: client.get_storage("System", "Account", ["0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"])
|
119
|
+
# DoubleMap: client.get_storage("ImOnline", "AuthoredBlocks", [2818, "0x749ddc93a65dfec3af27cc7478212cb7d4b0c0357fef35a0163966ab5333b757"])
|
120
|
+
def get_storage(module_name, storage_name, params = nil, block_hash = nil)
|
121
|
+
self.init_runtime(block_hash)
|
138
122
|
|
139
|
-
|
123
|
+
storage_key, return_type = SubstrateClient::Helper.generate_storage_key_from_metadata(@metadata, module_name, storage_name, params)
|
140
124
|
|
141
|
-
result = self.
|
125
|
+
result = self.state_getStorage(storage_key, block_hash)
|
142
126
|
return unless result
|
143
|
-
Scale::Types.get(return_type).decode(Scale::Bytes.new(result))
|
144
|
-
rescue => ex
|
145
|
-
puts ex.message
|
146
|
-
puts ex.backtrace
|
127
|
+
Scale::Types.get(return_type).decode(Scale::Bytes.new(result))
|
147
128
|
end
|
148
129
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
if params
|
155
|
-
params.each_with_index do |param, index|
|
156
|
-
if index == 0
|
157
|
-
param_hasher = hasher
|
158
|
-
elsif index == 1
|
159
|
-
param_hasher = hasher2
|
160
|
-
else
|
161
|
-
raise "Unexpected third parameter for storage call"
|
162
|
-
end
|
163
|
-
|
164
|
-
param_key = param.hex_to_bytes
|
165
|
-
param_hasher = "Twox128" if param_hasher.nil?
|
166
|
-
storage_hash += Crypto.send param_hasher.underscore, param_key
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
"0x#{storage_hash}"
|
171
|
-
else
|
172
|
-
# TODO: add test
|
173
|
-
storage_hash = storage_module_name + " " + storage_function_name
|
174
|
-
|
175
|
-
unless params.nil?
|
176
|
-
params = [params] if params.class != ::Array
|
177
|
-
params_key = params.join("")
|
178
|
-
hasher = "Twox128" if hasher.nil?
|
179
|
-
storage_hash += params_key.hex_to_bytes.bytes_to_utf8
|
180
|
-
end
|
181
|
-
|
182
|
-
"0x#{Crypto.send( hasher.underscore, storage_hash )}"
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
# chain_unsubscribe_runtime_version
|
187
|
-
# =>
|
188
|
-
# chain_unsubscribeRuntimeVersion
|
189
|
-
def real_method_name(method_name)
|
190
|
-
segments = method_name.to_s.split("_")
|
191
|
-
segments[0] + "_" + segments[1] + segments[2..].map(&:capitalize).join
|
192
|
-
end
|
130
|
+
def generate_storage_key(module_name, storage_name, params = nil, block_hash = nil)
|
131
|
+
self.init_runtime(block_hash)
|
132
|
+
SubstrateClient::Helper.generate_storage_key_from_metadata(@metadata, module_name, storage_name, params)
|
133
|
+
end
|
193
134
|
|
135
|
+
# compose_call "Balances", "Transfer", { dest: "0x586cb27c291c813ce74e86a60dad270609abf2fc8bee107e44a80ac00225c409", value: 1_000_000_000_000 }
|
136
|
+
def compose_call(module_name, call_name, params, block_hash=nil)
|
137
|
+
self.init_runtime(block_hash)
|
138
|
+
SubstrateClient::Helper.compose_call_from_metadata(@metadata, module_name, call_name, params)
|
194
139
|
end
|
195
140
|
|
141
|
+
def generate_storage_hash_from_data(storage_hex_data)
|
142
|
+
"0x" + Crypto.blake2_256(Scale::Bytes.new(storage_hex_data).bytes)
|
143
|
+
end
|
196
144
|
|
197
145
|
end
|
198
|
-
|
data/substrate_client.gemspec
CHANGED
@@ -36,11 +36,10 @@ Gem::Specification.new do |spec|
|
|
36
36
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
37
37
|
spec.require_paths = ["lib"]
|
38
38
|
|
39
|
-
spec.add_dependency "substrate_common.rb", "~> 0.1.8"
|
40
|
-
spec.add_dependency "faye-websocket", "~> 0.10.9"
|
41
|
-
spec.add_dependency "eventmachine", "~> 1.2.7"
|
42
39
|
spec.add_dependency "activesupport", "~> 5.2.4"
|
43
|
-
spec.add_dependency "scale.rb", "~> 0.2.
|
40
|
+
spec.add_dependency "scale.rb", "~> 0.2.11"
|
41
|
+
spec.add_dependency "kontena-websocket-client", "~> 0.1.1"
|
42
|
+
spec.add_dependency "substrate_common.rb", "~> 0.1.9"
|
44
43
|
|
45
44
|
spec.add_development_dependency "bundler", "~> 1.17"
|
46
45
|
spec.add_development_dependency "rake", ">= 12.3.3"
|
metadata
CHANGED
@@ -1,85 +1,71 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: substrate_client.rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Wu Minzhe
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-11-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: 0.1.8
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - "~>"
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: 0.1.8
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: faye-websocket
|
14
|
+
name: activesupport
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
30
16
|
requirements:
|
31
17
|
- - "~>"
|
32
18
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
19
|
+
version: 5.2.4
|
34
20
|
type: :runtime
|
35
21
|
prerelease: false
|
36
22
|
version_requirements: !ruby/object:Gem::Requirement
|
37
23
|
requirements:
|
38
24
|
- - "~>"
|
39
25
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
26
|
+
version: 5.2.4
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
28
|
+
name: scale.rb
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
44
30
|
requirements:
|
45
31
|
- - "~>"
|
46
32
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
33
|
+
version: 0.2.11
|
48
34
|
type: :runtime
|
49
35
|
prerelease: false
|
50
36
|
version_requirements: !ruby/object:Gem::Requirement
|
51
37
|
requirements:
|
52
38
|
- - "~>"
|
53
39
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
40
|
+
version: 0.2.11
|
55
41
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
42
|
+
name: kontena-websocket-client
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
58
44
|
requirements:
|
59
45
|
- - "~>"
|
60
46
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
47
|
+
version: 0.1.1
|
62
48
|
type: :runtime
|
63
49
|
prerelease: false
|
64
50
|
version_requirements: !ruby/object:Gem::Requirement
|
65
51
|
requirements:
|
66
52
|
- - "~>"
|
67
53
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
54
|
+
version: 0.1.1
|
69
55
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
56
|
+
name: substrate_common.rb
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
72
58
|
requirements:
|
73
59
|
- - "~>"
|
74
60
|
- !ruby/object:Gem::Version
|
75
|
-
version: 0.
|
61
|
+
version: 0.1.9
|
76
62
|
type: :runtime
|
77
63
|
prerelease: false
|
78
64
|
version_requirements: !ruby/object:Gem::Requirement
|
79
65
|
requirements:
|
80
66
|
- - "~>"
|
81
67
|
- !ruby/object:Gem::Version
|
82
|
-
version: 0.
|
68
|
+
version: 0.1.9
|
83
69
|
- !ruby/object:Gem::Dependency
|
84
70
|
name: bundler
|
85
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -141,7 +127,6 @@ email:
|
|
141
127
|
- wuminzhe@gmail.com
|
142
128
|
executables:
|
143
129
|
- metadata
|
144
|
-
- substrate_client
|
145
130
|
extensions: []
|
146
131
|
extra_rdoc_files: []
|
147
132
|
files:
|
@@ -149,6 +134,7 @@ files:
|
|
149
134
|
- ".rspec"
|
150
135
|
- ".travis.yml"
|
151
136
|
- CODE_OF_CONDUCT.md
|
137
|
+
- Dockerfile
|
152
138
|
- Gemfile
|
153
139
|
- Gemfile.lock
|
154
140
|
- LICENSE.txt
|
@@ -157,7 +143,7 @@ files:
|
|
157
143
|
- bin/console
|
158
144
|
- bin/setup
|
159
145
|
- exe/metadata
|
160
|
-
-
|
146
|
+
- lib/helper.rb
|
161
147
|
- lib/substrate_client.rb
|
162
148
|
- lib/substrate_client/version.rb
|
163
149
|
- substrate_client.gemspec
|
data/exe/substrate_client
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require "substrate_client"
|
4
|
-
require "json"
|
5
|
-
|
6
|
-
client = SubstrateClient.new("wss://cc3-5.kusama.network/")
|
7
|
-
# puts client.system_name
|
8
|
-
# puts client.system_chain
|
9
|
-
# puts client.system_version
|
10
|
-
# puts client.system_peers
|
11
|
-
# puts client.state_getStorage "0x0b76934f4cc08dee01012d059e1b83ee5e0621c4869aa60c02be9adcc98a0d1d"
|
12
|
-
# puts client.state_getChildKeys "0x","0x",1,"0x"
|
13
|
-
# puts client.runtime_getState "System", "Events",[],"0xe0303fbe2bc8482e06fb649f934c41349d12a25bd618c92e3331a3347e434f7b"
|
14
|
-
# puts client.rpc_methods
|
15
|
-
|
16
|
-
client.init
|
17
|
-
metadata = client.metadata
|
18
|
-
puts JSON.pretty_generate(metadata)
|