substrate_client.rb 0.1.4 → 0.1.9

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: 5f0802284ca7abb1fd02f502506a00ba67ef2c0d436a1bede7012fc7746196c9
4
- data.tar.gz: 2b76f455d30a57c0b36eceecc5cf19e720126e9f071e3899b054a151bb1b122c
3
+ metadata.gz: c9993008c06625166a02238e5efddd9fe0c840f4c00e7a3dd053d58a9e7ec5a8
4
+ data.tar.gz: f360d71d6ba20d8ec5ed56d00ba8651a6a5371d88399c2a7113f1315a4b4f2ce
5
5
  SHA512:
6
- metadata.gz: 392f72952f9822de5dff8b6c5df89b7eab52e7bdf38c988f0ea4448c8f6439f74dd06e415dd1fc33c627ed4c74e494c8838c452d3975e1f92c89a8b86e4e68a9
7
- data.tar.gz: eda7bacb9cb728d254c3582ef99696689fe03d387b14ad38ad4196b1c4a2ed10eb3f806a8c01c1da9c9143ffe00a15e1597b534c2df9ac1876d62fd63a1be230
6
+ metadata.gz: 3a2158b190ebf549901de66c5b590a0780177149427efb77439db24b6756f2b2890de5ab91010448338e8a8da48ce8be6f917428f35e52cd1e578af637746da8
7
+ data.tar.gz: 3c46a63f1173b4ae417c76fd5325a612b4f22e299483cf6adac447914a8e1a556dc96c4559af29eadfb308645a1950d659d67aa82a07af7587f833a128cd495a
@@ -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,16 +1,16 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- substrate_client.rb (0.1.4)
4
+ substrate_client.rb (0.1.9)
5
5
  activesupport (~> 5.2.4)
6
- eventmachine (~> 1.2.7)
7
- faye-websocket (~> 0.10.9)
8
- scale.rb (~> 0.2.5)
6
+ kontena-websocket-client (~> 0.1.1)
7
+ scale.rb (~> 0.2.12)
8
+ substrate_common.rb (~> 0.1.9)
9
9
 
10
10
  GEM
11
11
  remote: https://rubygems.org/
12
12
  specs:
13
- activesupport (5.2.4.2)
13
+ activesupport (5.2.4.4)
14
14
  concurrent-ruby (~> 1.0, >= 1.0.2)
15
15
  i18n (>= 0.7, < 2)
16
16
  minitest (~> 5.1)
@@ -18,17 +18,15 @@ GEM
18
18
  base58 (0.2.3)
19
19
  blake2b (0.10.0)
20
20
  coderay (1.1.2)
21
- concurrent-ruby (1.1.6)
21
+ concurrent-ruby (1.1.7)
22
22
  diff-lcs (1.3)
23
- eventmachine (1.2.7)
24
- faye-websocket (0.10.9)
25
- eventmachine (>= 0.12.0)
26
- websocket-driver (>= 0.5.1)
27
- i18n (1.8.2)
23
+ i18n (1.8.5)
28
24
  concurrent-ruby (~> 1.0)
29
- json (2.3.0)
25
+ json (2.3.1)
26
+ kontena-websocket-client (0.1.1)
27
+ websocket-driver (~> 0.6.5)
30
28
  method_source (0.9.2)
31
- minitest (5.14.0)
29
+ minitest (5.14.2)
32
30
  pry (0.12.2)
33
31
  coderay (~> 1.1.0)
34
32
  method_source (~> 0.9.0)
@@ -46,12 +44,12 @@ GEM
46
44
  diff-lcs (>= 1.2.0, < 2.0)
47
45
  rspec-support (~> 3.9.0)
48
46
  rspec-support (3.9.2)
49
- scale.rb (0.2.5)
47
+ scale.rb (0.2.12)
50
48
  activesupport (>= 4.0.0)
51
49
  json (~> 2.3.0)
52
- substrate_common.rb (~> 0.1.8)
50
+ substrate_common.rb (~> 0.1.9)
53
51
  thor (~> 0.19.0)
54
- substrate_common.rb (0.1.8)
52
+ substrate_common.rb (0.1.9)
55
53
  base58
56
54
  blake2b
57
55
  xxhash
@@ -59,9 +57,9 @@ GEM
59
57
  thread_safe (0.3.6)
60
58
  tzinfo (1.2.7)
61
59
  thread_safe (~> 0.1)
62
- websocket-driver (0.7.1)
60
+ websocket-driver (0.6.5)
63
61
  websocket-extensions (>= 0.1.0)
64
- websocket-extensions (0.1.4)
62
+ websocket-extensions (0.1.5)
65
63
  xxhash (0.4.0)
66
64
 
67
65
  PLATFORMS
data/README.md CHANGED
@@ -22,184 +22,153 @@ Or install it yourself as:
22
22
 
23
23
  ### Supported rpc methods
24
24
 
25
- #### rpc method list
26
-
27
25
  ```ruby
28
26
  require "substrate_client"
29
27
 
30
28
  client = SubstrateClient.new("wss://kusama-rpc.polkadot.io/")
31
- puts client.method_list
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
+ ]
32
41
  ```
33
- The rpc methods can be dynamically called by its name, so the methods returned by this method can all be used.
34
-
35
- #### hard-coded rpc method
36
-
37
- But, in order to show the parameters more clearly, some important or frequently used methods are hard-coded:
38
-
39
- - chain_get_finalised_head
40
-
41
- Get hash of the last finalized block in the canon chain
42
-
43
-
44
-
45
- - chain_get_head
46
-
47
- - chain_get_header(block_hash = nil)
48
-
49
- Retrieves the header for a specific block
50
-
51
-
52
-
53
- - chain_get_block(block_hash = nil)
54
-
55
- Get header and body of a relay chain block
56
-
57
-
58
-
59
- - chain_get_block_hash(block_id)
60
-
61
- Get the block hash for a specific block
62
-
63
-
64
-
65
- - chain_get_runtime_version(block_hash = nil)
66
-
67
- Get the runtime version for a specific block
68
-
69
-
70
-
71
- - state_get_metadata(block_hash = nil)
72
-
73
- Returns the runtime metadata by block
74
-
75
-
76
-
77
- - state_get_storage(storage_key, block_hash = nil)
78
-
79
- Retrieves the storage for a key
80
-
81
-
82
-
83
- - system_name
84
-
85
- - system_version
86
-
87
-
88
-
89
- - chain_subscribe_all_heads(&callback)
90
-
91
- Retrieves the newest header via subscription. This will return data continuously until you unsubscribe the subscription.
92
-
93
- ```ruby
94
- subscription = client.chain_subscribe_new_heads do |data|
95
- p data
96
- end
97
- ```
98
-
99
- - chain_unsubscribe_all_heads(subscription)
100
-
101
- Unsubscribe newest header subscription.
102
-
103
- ```ruby
104
- client.chain_unsubscribe_all_heads(subscription)
105
- ```
106
-
107
-
108
-
109
- - chain_subscribe_new_heads(&callback)
110
-
111
- Retrieves the best header via subscription. This will return data continuously until you unsubscribe the subscription.
112
-
113
- ```ruby
114
- subscription = client.chain_subscribe_new_heads do |data|
115
- p data
116
- end
117
- ```
118
-
119
- - chain_unsubscribe_new_heads(subscription)
120
42
 
121
- Unsubscribe the best header subscription.
43
+ The rpc methods can be dynamically called by its name, so you can call it like:
122
44
 
123
- ```ruby
124
- client.chain_unsubscribe_new_heads(subscription)
125
- ```
45
+ ```ruby
46
+ client.chain_getBlockHash(1024)
47
+ ```
126
48
 
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
127
110
 
111
+ These methods will encode the parameters and decode the returned data
128
112
 
129
- - chain_subscribe_finalized_heads(&callback)
113
+ - `get_block_number(block_hash)`
130
114
 
131
- Retrieves the best finalized header via subscription. This will return data continuously until you unsubscribe the subscription.
115
+ - `get_metadata(block_hash)`
132
116
 
133
- ```ruby
134
- subscription = client.chain_subscribe_finalized_heads do |data|
135
- p data
136
- end
137
- ```
117
+ - `get_block(block_hash=nil)`
138
118
 
139
- - chain_unsubscribe_finalized_heads(subscription)
119
+ - `get_block_events(block_hash)`
140
120
 
141
- Unsubscribe the best finalized header subscription.
121
+ - `get_storage(module_name, storage_name, params = nil, block_hash = nil)`
142
122
 
143
123
  ```ruby
144
- client.chain_unsubscribe_finalized_heads(subscription)
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)
145
127
  ```
146
-
147
-
148
-
149
- - state_subscribe_runtime_version(&callback)
150
-
151
- Retrieves the runtime version via subscription.
152
-
153
- - state_unsubscribe_runtime_version(subscription)
154
-
155
- Unsubscribe the runtime version subscription.
156
-
157
128
 
158
-
159
- - state_subscribe_storage(keys, &callback)
160
-
161
- Subscribes to storage changes for the provided keys until unsubscribe.
129
+ - `compose_call(module_name, call_name, params, block_hash=nil)`
162
130
 
163
131
  ```ruby
164
- subscription = client.state_subscribe_storage ["0x26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7"] do |data|
165
- p data
166
- end
132
+ client.compose_call "Balances", "Transfer", { dest: "0x586cb27c291c813ce74e86a60dad270609abf2fc8bee107e44a80ac00225c409", value: 1_000_000_000_000 }, nil
167
133
  ```
168
134
 
169
- - state_unsubscribe_storage(subscription)
170
-
171
- Unsubscribe storage changes.
172
-
173
-
174
- ### Cutom methods based on rpc methods
175
-
176
- These methods will encode the parameters and decode the returned data
135
+ ## Docker
177
136
 
178
- - get_block_number(block_hash)
137
+ 1. update to latest image
179
138
 
180
- - get_metadata(block_hash)
139
+ `docker pull itering/substrate_client:latest`
181
140
 
182
- - get_block(block_hash=nil)
141
+ 2. Run image:
183
142
 
184
- - get_block_events(block_hash)
143
+ `docker run -it itering/substrate_client:latest`
185
144
 
186
- - subscribe_block_events(&callback)
145
+ This will enter the container with a linux shell opened.
187
146
 
188
- - get_storage(module_name, storage_name, params = nil, block_hash = nil)
147
+ ```shell
148
+ /usr/src/app #
149
+ ```
189
150
 
190
- ```ruby
191
- client.get_storage("Sudo", "Key")
192
- client.get_storage("Balances", "TotalIssuance")
193
- client.get_storage("System", "Account", ["0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"])
194
- client.get_storage("ImOnline", "AuthoredBlocks", [2818, "0x749ddc93a65dfec3af27cc7478212cb7d4b0c0357fef35a0163966ab5333b757"])
195
- ```
151
+ 3. Type `rspec` to run all tests
196
152
 
197
- - compose_call(module_name, call_name, params, block_hash=nil)
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
+ ```
198
160
 
199
- ```ruby
200
- compose_call "Balances", "Transfer", { dest: "0x586cb27c291c813ce74e86a60dad270609abf2fc8bee107e44a80ac00225c409", value: 1_000_000_000_000 }
201
- ```
161
+ 4. Or, type `./bin/console` to enter the ruby interactive environment and run any decode or encode code
202
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
+ ```
203
172
 
204
173
 
205
174
  ## Development
@@ -3,11 +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]
9
-
6
+ url = ARGV[0] || "wss://kusama-rpc.polkadot.io"
10
7
  client = SubstrateClient.new(url)
11
- client.init(block_hash)
8
+
9
+ block_hash = ARGV[1] || client.chain_getFinalisedHead
10
+
12
11
  metadata = client.get_metadata(block_hash)
13
- puts JSON.pretty_generate(metadata.value)
12
+ puts JSON.pretty_generate(metadata.value.to_human)
@@ -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
@@ -1,30 +1,46 @@
1
1
  require "substrate_client/version"
2
2
 
3
+ require "logger"
3
4
  require "scale.rb"
4
- require "faye/websocket"
5
- require "eventmachine"
6
5
  require "json"
7
6
  require "active_support"
8
7
  require "active_support/core_ext/string"
8
+ require "helper"
9
+ require 'kontena-websocket-client'
10
+
11
+ def ws_request(url, payload)
12
+ result = nil
13
+ Kontena::Websocket::Client.connect(url, {}) do |client|
14
+ client.send(payload.to_json)
15
+
16
+ client.read do |message|
17
+ result = JSON.parse message
18
+ client.close(1000)
19
+ end
20
+ end
21
+
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
27
+ end
9
28
 
10
29
  class SubstrateClient
30
+ class WebsocketError < StandardError; end
11
31
  class RpcError < StandardError; end
32
+ class RpcTimeout < StandardError; end
12
33
 
13
34
  attr_accessor :spec_name, :spec_version, :metadata
14
- attr_reader :ws
15
35
 
16
- def initialize(url: , spec_name: nil)
36
+ def initialize(url, spec_name: nil)
17
37
  @url = url
18
38
  @request_id = 1
19
39
  @spec_name = spec_name
20
- Scale::TypeRegistry.instance.load(spec_name)
21
-
22
- init_ws
40
+ Scale::TypeRegistry.instance.load(spec_name: spec_name)
23
41
  end
24
42
 
25
- def request(method, params, subscription_callback=nil)
26
- queue = Queue.new
27
-
43
+ def request(method, params)
28
44
  payload = {
29
45
  "jsonrpc" => "2.0",
30
46
  "method" => method,
@@ -32,15 +48,7 @@ class SubstrateClient
32
48
  "id" => @request_id
33
49
  }
34
50
 
35
- @callbacks[@request_id] = proc { |data| queue << data }
36
- @ws.send(payload.to_json)
37
- @request_id += 1
38
- data = queue.pop
39
-
40
- if not subscription_callback.nil? && data["result"]
41
- @subscription_callbacks[data["result"]] = subscription_callback
42
- end
43
-
51
+ data = ws_request(@url, payload)
44
52
  if data["error"]
45
53
  raise RpcError, data["error"]
46
54
  else
@@ -48,17 +56,9 @@ class SubstrateClient
48
56
  end
49
57
  end
50
58
 
51
- def init_runtime(block_hash: nil, block_id: nil)
52
- if block_hash.nil?
53
- if not block_id.nil?
54
- block_hash = self.chain_get_block_hash(block_id)
55
- else
56
- block_hash = self.chain_get_head
57
- end
58
- end
59
-
59
+ def init_runtime(block_hash=nil)
60
60
  # set current runtime spec version
61
- runtime_version = self.state_get_runtime_version(block_hash)
61
+ runtime_version = self.state_getRuntimeVersion(block_hash)
62
62
  @spec_version = runtime_version["specVersion"]
63
63
  Scale::TypeRegistry.instance.spec_version = @spec_version
64
64
 
@@ -73,349 +73,73 @@ class SubstrateClient
73
73
  request(method, params)
74
74
  end
75
75
 
76
- def rpc_method(method_name)
77
- SubstrateClient.real_method_name(method_name.to_s)
78
- end
79
-
80
76
  # ################################################
81
77
  # origin rpc methods
82
78
  # ################################################
83
79
  def method_missing(method, *args)
84
- rpc_method = SubstrateClient.real_method_name(method)
85
- invoke rpc_method, *args
86
- end
87
-
88
- def rpc_methods
89
- invoke rpc_method(__method__)
90
- end
91
-
92
- def chain_get_head
93
- invoke rpc_method(__method__)
94
- end
95
-
96
- def chain_get_finalised_head
97
- invoke rpc_method(__method__)
98
- end
99
-
100
- def chain_get_header(block_hash = nil)
101
- invoke rpc_method(__method__), block_hash
102
- end
103
-
104
- def chain_get_block(block_hash = nil)
105
- invoke rpc_method(__method__), block_hash
106
- end
107
-
108
- def chain_get_block_hash(block_id)
109
- invoke rpc_method(__method__), block_id
110
- end
111
-
112
- def chain_get_runtime_version(block_hash = nil)
113
- invoke rpc_method(__method__), block_hash
114
- end
115
-
116
- def state_get_metadata(block_hash = nil)
117
- invoke rpc_method(__method__), block_hash
118
- end
119
-
120
- def state_get_storage(storage_key, block_hash = nil)
121
- invoke rpc_method(__method__), storage_key, block_hash
122
- end
123
-
124
- def system_name
125
- invoke rpc_method(__method__)
126
- end
127
-
128
- def system_version
129
- invoke rpc_method(__method__)
130
- end
131
-
132
- def chain_subscribe_all_heads(&callback)
133
- request rpc_method(__method__), [], callback
134
- end
135
-
136
- def chain_unsubscribe_all_heads(subscription)
137
- invoke rpc_method(__method__), subscription
138
- end
139
-
140
- def chain_subscribe_new_heads(&callback)
141
- request rpc_method(__method__), [], callback
142
- end
143
-
144
- def chain_unsubscribe_new_heads(subscription)
145
- invoke rpc_method(__method__), subscription
146
- end
147
-
148
- def chain_subscribe_finalized_heads(&callback)
149
- request rpc_method(__method__), [], callback
150
- end
151
-
152
- def chain_unsubscribe_finalized_heads(subscription)
153
- invoke rpc_method(__method__), subscription
154
- end
155
-
156
- def state_subscribe_runtime_version(&callback)
157
- request rpc_method(__method__), [], callback
158
- end
159
-
160
- def state_unsubscribe_runtime_version(subscription)
161
- invoke rpc_method(__method__), subscription
162
- end
163
-
164
- def state_subscribe_storage(keys, &callback)
165
- request rpc_method(__method__), [keys], callback
166
- end
167
-
168
- def state_unsubscribe_storage(subscription)
169
- invoke rpc_method(__method__), subscription
80
+ invoke method, *args
170
81
  end
171
82
 
172
83
  # ################################################
173
84
  # custom methods based on origin rpc methods
174
85
  # ################################################
175
- def method_list
176
- self.rpc_methods["methods"].map(&:underscore)
86
+ def methods
87
+ invoke("rpc_methods")["methods"]
177
88
  end
178
89
 
179
90
  def get_block_number(block_hash)
180
- header = self.chain_get_header(block_hash)
91
+ header = self.chain_getHeader(block_hash)
181
92
  header["number"].to_i(16)
182
93
  end
183
94
 
184
- def get_metadata(block_hash)
185
- hex = self.state_get_metadata(block_hash)
95
+ def get_metadata(block_hash=nil)
96
+ hex = self.state_getMetadata(block_hash)
186
97
  Scale::Types::Metadata.decode(Scale::Bytes.new(hex))
187
98
  end
188
99
 
189
100
  def get_block(block_hash=nil)
190
- self.init_runtime(block_hash: block_hash)
191
- block = self.chain_get_block(block_hash)
192
-
193
- block["block"]["header"]["number"] = block["block"]["header"]["number"].to_i(16)
194
-
195
- block["block"]["extrinsics"].each_with_index do |hex, i|
196
- scale_bytes = Scale::Bytes.new(hex)
197
- block["block"]["extrinsics"][i] = Scale::Types::Extrinsic.decode(scale_bytes).to_human
198
- end
199
-
200
- block['block']['header']["digest"]["logs"].each_with_index do |hex, i|
201
- scale_bytes = Scale::Bytes.new(hex)
202
- block['block']['header']["digest"]["logs"][i] = Scale::Types::LogDigest.decode(scale_bytes).to_human
203
- end
204
-
205
- block
101
+ self.init_runtime(block_hash)
102
+ block = self.chain_getBlock(block_hash)
103
+ SubstrateClient::Helper.decode_block(block)
206
104
  end
207
105
 
208
- def get_block_events(block_hash)
209
- self.init_runtime(block_hash: block_hash)
106
+ def get_block_events(block_hash=nil)
107
+ self.init_runtime(block_hash)
210
108
 
211
109
  storage_key = "0x26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7"
212
- events_data = state_get_storage storage_key, block_hash
110
+ events_data = state_getStorage storage_key, block_hash
213
111
 
214
112
  scale_bytes = Scale::Bytes.new(events_data)
215
113
  Scale::Types.get("Vec<EventRecord>").decode(scale_bytes).to_human
216
114
  end
217
115
 
218
- def subscribe_block_events(&callback)
219
- self.chain_subscribe_finalised_heads do |data|
220
-
221
- block_number = data["params"]["result"]["number"].to_i(16) - 1
222
- block_hash = data["params"]["result"]["parentHash"]
223
-
224
- EM.defer(
225
-
226
- proc {
227
- events = get_block_events block_hash
228
- { block_number: block_number, events: events }
229
- },
230
-
231
- proc { |result|
232
- begin
233
- callback.call result
234
- rescue => ex
235
- puts ex.message
236
- puts ex.backtrace.join("\n")
237
- end
238
- },
239
-
240
- proc { |e|
241
- puts e
242
- }
243
-
244
- )
245
- end
246
- end
247
-
248
116
  # Plain: client.get_storage("Sudo", "Key")
249
117
  # Plain: client.get_storage("Balances", "TotalIssuance")
250
118
  # Map: client.get_storage("System", "Account", ["0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"])
251
119
  # DoubleMap: client.get_storage("ImOnline", "AuthoredBlocks", [2818, "0x749ddc93a65dfec3af27cc7478212cb7d4b0c0357fef35a0163966ab5333b757"])
252
120
  def get_storage(module_name, storage_name, params = nil, block_hash = nil)
253
- self.init_runtime(block_hash: block_hash)
121
+ self.init_runtime(block_hash)
254
122
 
255
- # find the storage item from metadata
256
- metadata_modules = metadata.value.value[:metadata][:modules]
257
- metadata_module = metadata_modules.detect { |mm| mm[:name] == module_name }
258
- raise "Module '#{module_name}' not exist" unless metadata_module
259
- storage_item = metadata_module[:storage][:items].detect { |item| item[:name] == storage_name }
260
- raise "Storage item '#{storage_name}' not exist. \n#{metadata_module.inspect}" unless storage_item
123
+ storage_key, return_type = SubstrateClient::Helper.generate_storage_key_from_metadata(@metadata, module_name, storage_name, params)
261
124
 
262
- if storage_item[:type][:Plain]
263
- return_type = storage_item[:type][:Plain]
264
- elsif map = storage_item[:type][:Map]
265
- raise "Storage call of type \"Map\" requires 1 parameter" if params.nil? || params.length != 1
266
-
267
- hasher = map[:hasher]
268
- return_type = map[:value]
269
- # TODO: decode to account id if param is address
270
- # params[0] = decode(params[0]) if map[:key] == "AccountId"
271
- params[0] = Scale::Types.get(map[:key]).new(params[0]).encode
272
- elsif map = storage_item[:type][:DoubleMap]
273
- raise "Storage call of type \"DoubleMapType\" requires 2 parameters" if params.nil? || params.length != 2
274
-
275
- hasher = map[:hasher]
276
- hasher2 = map[:key2Hasher]
277
- return_type = map[:value]
278
- params[0] = Scale::Types.get(map[:key1]).new(params[0]).encode
279
- params[1] = Scale::Types.get(map[:key2]).new(params[1]).encode
280
- else
281
- raise NotImplementedError
282
- end
283
-
284
- storage_hash = SubstrateClient.generate_storage_hash(
285
- module_name,
286
- storage_name,
287
- params,
288
- hasher,
289
- hasher2,
290
- metadata.value.value[:metadata][:version]
291
- )
292
-
293
- result = self.state_get_storage(storage_hash, block_hash)
125
+ result = self.state_getStorage(storage_key, block_hash)
294
126
  return unless result
295
127
  Scale::Types.get(return_type).decode(Scale::Bytes.new(result))
296
128
  end
297
129
 
298
- # compose_call "Balances", "Transfer", { dest: "0x586cb27c291c813ce74e86a60dad270609abf2fc8bee107e44a80ac00225c409", value: 1_000_000_000_000 }
299
- def compose_call(module_name, call_name, params, block_hash=nil)
300
- self.init_runtime(block_hash: block_hash)
301
-
302
- call = metadata.get_module_call(module_name, call_name)
303
-
304
- value = {
305
- call_index: call[:lookup],
306
- module_name: module_name,
307
- call_name: call_name,
308
- params: []
309
- }
310
-
311
- params.keys.each_with_index do |call_param_name, i|
312
- param_value = params[call_param_name]
313
- value[:params] << {
314
- name: call_param_name.to_s,
315
- type: call[:args][i][:type],
316
- value: param_value
317
- }
318
- end
319
-
320
- Scale::Types::Extrinsic.new(value).encode
321
- end
322
-
323
- class << self
324
- def generate_storage_hash(module_name, storage_name, params = nil, hasher = nil, hasher2 = nil, metadata_version = nil)
325
- if metadata_version and metadata_version >= 9
326
- storage_hash = Crypto.twox128(module_name) + Crypto.twox128(storage_name)
327
-
328
- params&.each_with_index do |param, index|
329
- if index == 0
330
- param_hasher = hasher
331
- elsif index == 1
332
- param_hasher = hasher2
333
- else
334
- raise "Unexpected third parameter for storage call"
335
- end
336
-
337
- param_key = param.hex_to_bytes
338
- param_hasher = "Twox128" if param_hasher.nil?
339
- storage_hash += Crypto.send param_hasher.underscore, param_key
340
- end
341
-
342
- "0x#{storage_hash}"
343
- else
344
- # TODO: add test
345
- storage_hash = module_name + " " + storage_name
346
-
347
- unless params.nil?
348
- params = [params] if params.class != ::Array
349
- params_key = params.join("")
350
- hasher = "Twox128" if hasher.nil?
351
- storage_hash += params_key.hex_to_bytes.bytes_to_utf8
352
- end
353
-
354
- "0x#{Crypto.send( hasher.underscore, storage_hash )}"
355
- end
356
- end
357
-
358
- # chain_unsubscribe_runtime_version
359
- # =>
360
- # chain_unsubscribeRuntimeVersion
361
- def real_method_name(method_name)
362
- segments = method_name.to_s.split("_")
363
- segments[0] + "_" + segments[1] + segments[2..].map(&:capitalize).join
364
- end
365
-
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)
366
133
  end
367
134
 
368
- private
369
- def init_ws
370
- queue = Queue.new
371
-
372
- Thread.new do
373
- EM.run do
374
- start_connection
375
- queue << "ok"
376
- end
377
- end
378
-
379
- if queue.pop
380
- Thread.new do
381
- loop do
382
- if @ws && @ws.ready_state == 3
383
- puts "try to reconnect"
384
- start_connection
385
- end
386
-
387
- sleep(3)
388
- end
389
- end
390
- end
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)
391
139
  end
392
140
 
393
- def start_connection
394
- @callbacks = {}
395
- @subscription_callbacks = {}
396
-
397
- @ws = Faye::WebSocket::Client.new(@url)
398
- @ws.on :message do |event|
399
- # p [:message, event.data]
400
- if event.data.include?("jsonrpc")
401
- begin
402
- data = JSON.parse event.data
403
-
404
- if data["params"]
405
- if @subscription_callbacks[data["params"]["subscription"]]
406
- @subscription_callbacks[data["params"]["subscription"]].call data
407
- end
408
- else
409
- @callbacks[data["id"]].call data
410
- @callbacks.delete(data["id"])
411
- end
412
-
413
- rescue => ex
414
- puts ex.message
415
- puts ex.backtrace.join("\n")
416
- end
417
- end
418
- end
141
+ def generate_storage_hash_from_data(storage_hex_data)
142
+ "0x" + Crypto.blake2_256(Scale::Bytes.new(storage_hex_data).bytes)
419
143
  end
420
144
 
421
145
  end
@@ -1,3 +1,3 @@
1
1
  class SubstrateClient
2
- VERSION = "0.1.4"
2
+ VERSION = "0.1.9"
3
3
  end
@@ -36,10 +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 "faye-websocket", "~> 0.10.9"
40
- spec.add_dependency "eventmachine", "~> 1.2.7"
41
39
  spec.add_dependency "activesupport", "~> 5.2.4"
42
- spec.add_dependency "scale.rb", "~> 0.2.5"
40
+ spec.add_dependency "scale.rb", "~> 0.2.12"
41
+ spec.add_dependency "kontena-websocket-client", "~> 0.1.1"
42
+ spec.add_dependency "substrate_common.rb", "~> 0.1.9"
43
43
 
44
44
  spec.add_development_dependency "bundler", "~> 1.17"
45
45
  spec.add_development_dependency "rake", ">= 12.3.3"
metadata CHANGED
@@ -1,71 +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
4
+ version: 0.1.9
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-05-14 00:00:00.000000000 Z
11
+ date: 2020-11-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: faye-websocket
14
+ name: activesupport
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.10.9
19
+ version: 5.2.4
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.10.9
26
+ version: 5.2.4
27
27
  - !ruby/object:Gem::Dependency
28
- name: eventmachine
28
+ name: scale.rb
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 1.2.7
33
+ version: 0.2.12
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 1.2.7
40
+ version: 0.2.12
41
41
  - !ruby/object:Gem::Dependency
42
- name: activesupport
42
+ name: kontena-websocket-client
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 5.2.4
47
+ version: 0.1.1
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 5.2.4
54
+ version: 0.1.1
55
55
  - !ruby/object:Gem::Dependency
56
- name: scale.rb
56
+ name: substrate_common.rb
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 0.2.5
61
+ version: 0.1.9
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 0.2.5
68
+ version: 0.1.9
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: bundler
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -134,6 +134,7 @@ files:
134
134
  - ".rspec"
135
135
  - ".travis.yml"
136
136
  - CODE_OF_CONDUCT.md
137
+ - Dockerfile
137
138
  - Gemfile
138
139
  - Gemfile.lock
139
140
  - LICENSE.txt
@@ -142,6 +143,7 @@ files:
142
143
  - bin/console
143
144
  - bin/setup
144
145
  - exe/metadata
146
+ - lib/helper.rb
145
147
  - lib/substrate_client.rb
146
148
  - lib/substrate_client/version.rb
147
149
  - substrate_client.gemspec