substrate_client.rb 0.1.4 → 0.1.9

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