substrate_client.rb 0.1.5 → 0.1.6

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: 6ffef7ed87e2488047c796e8a77cc3e0789f532891a69283f6339b9dc55e899d
4
- data.tar.gz: '08806295aaf4ced6714042496be2a54408fc4c1ba8d5bebbfe352b431407ec02'
3
+ metadata.gz: 7a1478123f2f6a7365eab00d5bf908d93902c2b058d058207b9ed12531dc6f7a
4
+ data.tar.gz: d2df429bec159846b09646e91daa6f1050c2e1d206ed3f2c6347ad4240da8095
5
5
  SHA512:
6
- metadata.gz: ede3bdec9f513b5586f8d9404c2f55e8ab70d53cb70e8f87920da8ecaab5f605542bf1f5fef4137d267088f11591abf1b1624530f9e97f01d779c60015c876ff
7
- data.tar.gz: 967fa99d7887a30bade8a3914cfed8496a0a821f9ffc101405608b941fb406418fcacbd95be499ed6cd28960b0eadc9b1c98e733828783814e58f5ea44b18336
6
+ metadata.gz: 882b6345105fa751c1c42f3b5ffcc86872ba47e261a6598a1c094bc5332f7632b8238829c7d8ca261244ba4c3b0e797b82f937878a3172c2f104cfe1ba545721
7
+ data.tar.gz: 271d1ebb55ac8686eb9d047eb6b38f00b08f465fed392ec5d79f56b492b8bf303647913ef53b25b1c167f119b37b439cea6eb0fb1bd5b6aa62eb3e59f363de2f
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- substrate_client.rb (0.1.5)
4
+ substrate_client.rb (0.1.6)
5
5
  activesupport (~> 5.2.4)
6
6
  eventmachine (~> 1.2.7)
7
7
  faye-websocket (~> 0.10.9)
data/README.md CHANGED
@@ -28,61 +28,77 @@ Or install it yourself as:
28
28
  require "substrate_client"
29
29
 
30
30
  client = SubstrateClient.new("wss://kusama-rpc.polkadot.io/")
31
- puts client.method_list
31
+ client.method_list do |methods|
32
+ p methods
33
+ end
32
34
  ```
33
35
  The rpc methods can be dynamically called by its name, so the methods returned by this method can all be used.
34
36
 
35
- #### hard-coded rpc method
37
+
38
+
39
+ #### rpc methods
36
40
 
37
41
  But, in order to show the parameters more clearly, some important or frequently used methods are hard-coded:
38
42
 
39
- - chain_get_finalised_head
43
+ - chain_get_finalised_head(&callback)
40
44
 
41
45
  Get hash of the last finalized block in the canon chain
42
46
 
47
+ ```ruby
48
+ client.chain_get_finalised_head do |head|
49
+ p head
50
+ end
51
+ ```
52
+
43
53
 
44
54
 
45
- - chain_get_head
55
+ - chain_get_head(&callback)
56
+
57
+ Retrieves the header
46
58
 
47
- - chain_get_header(block_hash = nil)
59
+
60
+
61
+ - chain_get_header(block_hash = nil, &callback)
48
62
 
49
63
  Retrieves the header for a specific block
50
64
 
51
65
 
52
66
 
53
- - chain_get_block(block_hash = nil)
67
+ - chain_get_block(block_hash = nil, &callback)
54
68
 
55
69
  Get header and body of a relay chain block
56
70
 
57
71
 
58
72
 
59
- - chain_get_block_hash(block_id)
73
+ - chain_get_block_hash(block_id, &callback)
60
74
 
61
75
  Get the block hash for a specific block
62
76
 
63
77
 
64
78
 
65
- - chain_get_runtime_version(block_hash = nil)
79
+ - chain_get_runtime_version(block_hash = nil, &callback)
66
80
 
67
81
  Get the runtime version for a specific block
68
82
 
69
83
 
70
84
 
71
- - state_get_metadata(block_hash = nil)
85
+ - state_get_metadata(block_hash = nil, &callback)
72
86
 
73
87
  Returns the runtime metadata by block
74
88
 
75
89
 
76
90
 
77
- - state_get_storage(storage_key, block_hash = nil)
91
+ - state_get_storage(storage_key, block_hash = nil, &callback)
78
92
 
79
93
  Retrieves the storage for a key
80
94
 
81
95
 
82
96
 
83
- - system_name
97
+ - system_name(&callback)
98
+
99
+
84
100
 
85
- - system_version
101
+ - system_version(&callback)
86
102
 
87
103
 
88
104
 
@@ -110,19 +126,10 @@ But, in order to show the parameters more clearly, some important or frequently
110
126
 
111
127
  Retrieves the best header via subscription. This will return data continuously until you unsubscribe the subscription.
112
128
 
113
- ```ruby
114
- subscription = client.chain_subscribe_new_heads do |data|
115
- p data
116
- end
117
- ```
118
-
119
129
  - chain_unsubscribe_new_heads(subscription)
120
130
 
121
131
  Unsubscribe the best header subscription.
122
132
 
123
- ```ruby
124
- client.chain_unsubscribe_new_heads(subscription)
125
- ```
126
133
 
127
134
 
128
135
 
@@ -130,22 +137,12 @@ But, in order to show the parameters more clearly, some important or frequently
130
137
 
131
138
  Retrieves the best finalized header via subscription. This will return data continuously until you unsubscribe the subscription.
132
139
 
133
- ```ruby
134
- subscription = client.chain_subscribe_finalized_heads do |data|
135
- p data
136
- end
137
- ```
138
-
139
140
  - chain_unsubscribe_finalized_heads(subscription)
140
141
 
141
142
  Unsubscribe the best finalized header subscription.
142
143
 
143
- ```ruby
144
- client.chain_unsubscribe_finalized_heads(subscription)
145
- ```
146
-
147
144
 
148
-
145
+
149
146
  - state_subscribe_runtime_version(&callback)
150
147
 
151
148
  Retrieves the runtime version via subscription.
@@ -175,33 +172,57 @@ But, in order to show the parameters more clearly, some important or frequently
175
172
 
176
173
  These methods will encode the parameters and decode the returned data
177
174
 
178
- - get_block_number(block_hash)
175
+ - get_block_number(block_hash, &callback)
179
176
 
180
- - get_metadata(block_hash)
177
+ - get_metadata(block_hash, &callback)
181
178
 
182
- - get_block(block_hash=nil)
179
+ - get_block(block_hash=nil, &callback)
183
180
 
184
- - get_block_events(block_hash)
181
+ - get_block_events(block_hash, &callback)
185
182
 
186
- - subscribe_block_events(&callback)
183
+ - subscribe_block_events(&callback, &callback)
187
184
 
188
- - get_storage(module_name, storage_name, params = nil, block_hash = nil)
185
+ - get_storage(module_name, storage_name, params = nil, block_hash = nil, &callback)
189
186
 
190
187
  ```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"])
188
+ client.get_storage("Balances", "TotalIssuance", nil, nil) do |storage|
189
+ p storage
190
+ end
191
+
192
+ client.get_storage("System", "Account", ["0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"], nil) do |storage|
193
+ p storage
194
+ end
195
+
196
+ client.get_storage("ImOnline", "AuthoredBlocks", [2818, "0x749ddc93a65dfec3af27cc7478212cb7d4b0c0357fef35a0163966ab5333b757"], nil) do |storage|
197
+ p storage
198
+ end
195
199
  ```
196
200
 
197
- - compose_call(module_name, call_name, params, block_hash=nil)
201
+ - compose_call(module_name, call_name, params, block_hash=nil, &callback)
198
202
 
199
203
  ```ruby
200
- compose_call "Balances", "Transfer", { dest: "0x586cb27c291c813ce74e86a60dad270609abf2fc8bee107e44a80ac00225c409", value: 1_000_000_000_000 }
204
+ compose_call "Balances", "Transfer", { dest: "0x586cb27c291c813ce74e86a60dad270609abf2fc8bee107e44a80ac00225c409", value: 1_000_000_000_000 }, nil do |hex|
205
+ p hex
206
+ end
201
207
  ```
202
208
 
203
209
 
204
210
 
211
+ ## Synchronized client
212
+
213
+ There is also a synchronized version of the client, which is disconnected every time the data is retrieved. It is very convenient in some situations. But this version cannot use the subscription interface.
214
+
215
+ ```ruby
216
+ client = SubstrateClientSync.new "wss://kusama-rpc.polkadot.io/"
217
+ p client.method_list
218
+ p client.chain_get_head
219
+ p client.chain_get_finalised_head
220
+ p client.chain_get_header(block_hash = nil)
221
+ ...
222
+ ```
223
+
224
+
225
+
205
226
  ## Development
206
227
 
207
228
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
data/exe/metadata CHANGED
@@ -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]
6
+ url = ARGV[0] || "wss://kusama-rpc.polkadot.io/"
7
+ client = SubstrateClientSync.new(url)
8
+
9
+ block_hash = ARGV[1] || client.chain_get_finalised_head
9
10
 
10
- client = SubstrateClient.new(url)
11
- client.init(block_hash)
12
11
  metadata = client.get_metadata(block_hash)
13
- puts JSON.pretty_generate(metadata.value)
12
+ puts JSON.pretty_generate(metadata.value.to_human)
data/lib/helper.rb ADDED
@@ -0,0 +1,129 @@
1
+
2
+ class SubstrateClient::Helper
3
+ class << self
4
+ def generate_storage_hash_from_metadata(metadata, module_name, storage_name, params = nil)
5
+ # find the storage item from metadata
6
+ metadata_modules = metadata.value.value[:metadata][:modules]
7
+ metadata_module = metadata_modules.detect { |mm| mm[:name] == module_name }
8
+ raise "Module '#{module_name}' not exist" unless metadata_module
9
+ storage_item = metadata_module[:storage][:items].detect { |item| item[:name] == storage_name }
10
+ raise "Storage item '#{storage_name}' not exist. \n#{metadata_module.inspect}" unless storage_item
11
+
12
+ if storage_item[:type][:Plain]
13
+ return_type = storage_item[:type][:Plain]
14
+ elsif map = storage_item[:type][:Map]
15
+ raise "Storage call of type \"Map\" requires 1 parameter" if params.nil? || params.length != 1
16
+
17
+ hasher = map[:hasher]
18
+ return_type = map[:value]
19
+ # TODO: decode to account id if param is address
20
+ # params[0] = decode(params[0]) if map[:key] == "AccountId"
21
+ params[0] = Scale::Types.get(map[:key]).new(params[0]).encode
22
+ elsif map = storage_item[:type][:DoubleMap]
23
+ raise "Storage call of type \"DoubleMapType\" requires 2 parameters" if params.nil? || params.length != 2
24
+
25
+ hasher = map[:hasher]
26
+ hasher2 = map[:key2Hasher]
27
+ return_type = map[:value]
28
+ params[0] = Scale::Types.get(map[:key1]).new(params[0]).encode
29
+ params[1] = Scale::Types.get(map[:key2]).new(params[1]).encode
30
+ else
31
+ raise NotImplementedError
32
+ end
33
+
34
+ storage_hash = generate_storage_hash(
35
+ module_name,
36
+ storage_name,
37
+ params,
38
+ hasher,
39
+ hasher2,
40
+ metadata.value.value[:metadata][:version]
41
+ )
42
+ [storage_hash, return_type]
43
+ end
44
+
45
+ def generate_storage_hash(module_name, storage_name, params = nil, hasher = nil, hasher2 = nil, metadata_version = nil)
46
+ if metadata_version and metadata_version >= 9
47
+ storage_hash = Crypto.twox128(module_name) + Crypto.twox128(storage_name)
48
+
49
+ params&.each_with_index do |param, index|
50
+ if index == 0
51
+ param_hasher = hasher
52
+ elsif index == 1
53
+ param_hasher = hasher2
54
+ else
55
+ raise "Unexpected third parameter for storage call"
56
+ end
57
+
58
+ param_key = param.hex_to_bytes
59
+ param_hasher = "Twox128" if param_hasher.nil?
60
+ storage_hash += Crypto.send param_hasher.underscore, param_key
61
+ end
62
+
63
+ "0x#{storage_hash}"
64
+ else
65
+ # TODO: add test
66
+ storage_hash = module_name + " " + storage_name
67
+
68
+ unless params.nil?
69
+ params = [params] if params.class != ::Array
70
+ params_key = params.join("")
71
+ hasher = "Twox128" if hasher.nil?
72
+ storage_hash += params_key.hex_to_bytes.bytes_to_utf8
73
+ end
74
+
75
+ "0x#{Crypto.send( hasher.underscore, storage_hash )}"
76
+ end
77
+ end
78
+
79
+ def compose_call_from_metadata(metadata, module_name, call_name, params)
80
+ call = metadata.get_module_call(module_name, call_name)
81
+
82
+ value = {
83
+ call_index: call[:lookup],
84
+ module_name: module_name,
85
+ call_name: call_name,
86
+ params: []
87
+ }
88
+
89
+ params.keys.each_with_index do |call_param_name, i|
90
+ param_value = params[call_param_name]
91
+ value[:params] << {
92
+ name: call_param_name.to_s,
93
+ type: call[:args][i][:type],
94
+ value: param_value
95
+ }
96
+ end
97
+
98
+ Scale::Types::Extrinsic.new(value).encode
99
+ end
100
+
101
+ def decode_block(block)
102
+ block["block"]["header"]["number"] = block["block"]["header"]["number"].to_i(16)
103
+
104
+ block["block"]["extrinsics"].each_with_index do |hex, i|
105
+ scale_bytes = Scale::Bytes.new(hex)
106
+ block["block"]["extrinsics"][i] = Scale::Types::Extrinsic.decode(scale_bytes).to_human
107
+ end
108
+
109
+ block['block']['header']["digest"]["logs"].each_with_index do |hex, i|
110
+ scale_bytes = Scale::Bytes.new(hex)
111
+ block['block']['header']["digest"]["logs"][i] = Scale::Types::LogDigest.decode(scale_bytes).to_human
112
+ end
113
+
114
+ block
115
+ end
116
+
117
+ # chain_unsubscribe_runtime_version
118
+ # =>
119
+ # chain_unsubscribeRuntimeVersion
120
+ def real_method_name(method_name)
121
+ segments = method_name.to_s.split("_")
122
+ if segments.length == 1
123
+ segments[0]
124
+ else
125
+ segments[0] + "_" + segments[1] + segments[2..].map(&:capitalize).join
126
+ end
127
+ end
128
+ end
129
+ end
@@ -6,7 +6,9 @@ require "json"
6
6
  require "active_support"
7
7
  require "active_support/core_ext/string"
8
8
  require "websocket"
9
+ require "helper"
9
10
  require "timeout_queue"
11
+ require "substrate_client_sync"
10
12
 
11
13
  class SubstrateClient
12
14
  class RpcError < StandardError; end
@@ -28,15 +30,15 @@ class SubstrateClient
28
30
  Scale::TypeRegistry.instance.load(spec_name)
29
31
 
30
32
  init_ws
33
+
34
+ at_exit { self.close }
31
35
  end
32
36
 
33
37
  def close
34
38
  @ws.close
35
39
  end
36
40
 
37
- def request(method, params, subscription_callback=nil)
38
- queue = TimeoutQueue.new
39
-
41
+ def request(method, params, callback: nil, subscription_callback: nil)
40
42
  payload = {
41
43
  "jsonrpc" => "2.0",
42
44
  "method" => method,
@@ -44,193 +46,188 @@ class SubstrateClient
44
46
  "id" => @request_id
45
47
  }
46
48
 
47
- @callbacks[@request_id] = proc { |data| queue << data }
48
- @ws.send(payload.to_json)
49
- @request_id += 1
50
- data = queue.pop(true, 5)
51
-
52
- if not subscription_callback.nil? && data["result"]
53
- @subscription_callbacks[data["result"]] = subscription_callback
54
- end
49
+ @callbacks[@request_id] = proc do |data|
50
+ if not subscription_callback.nil? && data["result"]
51
+ @subscription_callbacks[data["result"]] = subscription_callback
52
+ end
55
53
 
56
- if data["error"]
57
- raise RpcError, data["error"]
58
- else
59
- data["result"]
54
+ callback.call data if callback
60
55
  end
61
- rescue ThreadError => ex
62
- raise RpcTimeout
56
+ @ws.send(payload.to_json)
57
+ @request_id += 1
63
58
  end
64
59
 
65
- def init_runtime(block_hash: nil, block_id: nil)
66
- if block_hash.nil?
67
- if not block_id.nil?
68
- block_hash = self.chain_get_block_hash(block_id)
69
- else
70
- block_hash = self.chain_get_head
60
+ def init_runtime(block_hash=nil, &callback)
61
+ # set current runtime spec version
62
+ self.state_get_runtime_version(block_hash) do |runtime_version|
63
+ @spec_version = runtime_version["specVersion"]
64
+ Scale::TypeRegistry.instance.spec_version = @spec_version
65
+
66
+ # set current metadata
67
+ self.get_metadata(block_hash) do |metadata|
68
+ @metadata = metadata
69
+ Scale::TypeRegistry.instance.metadata = @metadata.value
70
+ callback.call
71
71
  end
72
72
  end
73
+ end
73
74
 
74
- # set current runtime spec version
75
- runtime_version = self.state_get_runtime_version(block_hash)
76
- @spec_version = runtime_version["specVersion"]
77
- Scale::TypeRegistry.instance.spec_version = @spec_version
78
-
79
- # set current metadata
80
- @metadata = self.get_metadata(block_hash)
81
- Scale::TypeRegistry.instance.metadata = @metadata.value
82
- true
75
+ def do_init_runtime(block_hash, &callback)
83
76
  end
84
77
 
85
- def invoke(method, *params)
86
- # params.reject! { |param| param.nil? }
87
- request(method, params)
78
+ def invoke(method, params, callback)
79
+ request method, params, callback: proc { |data|
80
+ if data["error"]
81
+ Thread.main.raise RpcError, data["error"]
82
+ else
83
+ callback.call data["result"]
84
+ end
85
+ }
88
86
  end
89
87
 
90
88
  def rpc_method(method_name)
91
- SubstrateClient.real_method_name(method_name.to_s)
89
+ Helper.real_method_name(method_name.to_s)
92
90
  end
93
91
 
94
92
  # ################################################
95
93
  # origin rpc methods
96
94
  # ################################################
97
- def method_missing(method, *args)
98
- rpc_method = SubstrateClient.real_method_name(method)
99
- invoke rpc_method, *args
95
+ def method_missing(method, args, &callback)
96
+ rpc_method = Helper.real_method_name(method)
97
+ invoke rpc_method, args, callback
98
+ end
99
+
100
+ def state_get_runtime_version(block_hash=nil, &callback)
101
+ invoke rpc_method(__method__), [ block_hash ], callback
100
102
  end
101
103
 
102
- def rpc_methods
103
- invoke rpc_method(__method__)
104
+ def rpc_methods(&callback)
105
+ invoke rpc_method(__method__), [], callback
104
106
  end
105
107
 
106
- def chain_get_head
107
- invoke rpc_method(__method__)
108
+ def chain_get_head(&callback)
109
+ invoke rpc_method(__method__), [], callback
108
110
  end
109
111
 
110
112
  def chain_get_finalised_head
111
- invoke rpc_method(__method__)
113
+ invoke rpc_method(__method__), [], callback
112
114
  end
113
115
 
114
- def chain_get_header(block_hash = nil)
115
- invoke rpc_method(__method__), block_hash
116
+ def chain_get_header(block_hash = nil, &callback)
117
+ invoke rpc_method(__method__), [ block_hash ], callback
116
118
  end
117
119
 
118
- def chain_get_block(block_hash = nil)
119
- invoke rpc_method(__method__), block_hash
120
+ def chain_get_block(block_hash = nil, &callback)
121
+ invoke rpc_method(__method__), [ block_hash ], callback
120
122
  end
121
123
 
122
- def chain_get_block_hash(block_id)
123
- invoke rpc_method(__method__), block_id
124
+ def chain_get_block_hash(block_id, &callback)
125
+ invoke rpc_method(__method__), [ block_id ], callback
124
126
  end
125
127
 
126
- def chain_get_runtime_version(block_hash = nil)
127
- invoke rpc_method(__method__), block_hash
128
+ def chain_get_runtime_version(block_hash = nil, &callback)
129
+ invoke rpc_method(__method__), [ block_hash ], callback
128
130
  end
129
131
 
130
- def state_get_metadata(block_hash = nil)
131
- invoke rpc_method(__method__), block_hash
132
+ def state_get_metadata(block_hash = nil, &callback)
133
+ invoke rpc_method(__method__), [ block_hash ], callback
132
134
  end
133
135
 
134
- def state_get_storage(storage_key, block_hash = nil)
135
- invoke rpc_method(__method__), storage_key, block_hash
136
+ def state_get_storage(storage_key, block_hash = nil, &callback)
137
+ invoke rpc_method(__method__), [ storage_key, block_hash ], callback
136
138
  end
137
139
 
138
- def system_name
139
- invoke rpc_method(__method__)
140
+ def system_name(&callback)
141
+ invoke rpc_method(__method__), [], callback
140
142
  end
141
143
 
142
- def system_version
143
- invoke rpc_method(__method__)
144
+ def system_version(&callback)
145
+ invoke rpc_method(__method__), [], callback
144
146
  end
145
147
 
146
148
  def chain_subscribe_all_heads(&callback)
147
- request rpc_method(__method__), [], callback
149
+ request rpc_method(__method__), [], subscription_callback: callback
148
150
  end
149
151
 
150
- def chain_unsubscribe_all_heads(subscription)
151
- invoke rpc_method(__method__), subscription
152
+ def chain_unsubscribe_all_heads(subscription, &callback)
153
+ invoke rpc_method(__method__), [ subscription ], callback
152
154
  end
153
155
 
154
156
  def chain_subscribe_new_heads(&callback)
155
- request rpc_method(__method__), [], callback
157
+ request rpc_method(__method__), [], subscription_callback: callback
156
158
  end
157
159
 
158
- def chain_unsubscribe_new_heads(subscription)
159
- invoke rpc_method(__method__), subscription
160
+ def chain_unsubscribe_new_heads(subscription, &callback)
161
+ invoke rpc_method(__method__), [ subscription ], callback
160
162
  end
161
163
 
162
164
  def chain_subscribe_finalized_heads(&callback)
163
- request rpc_method(__method__), [], callback
165
+ request rpc_method(__method__), [], subscription_callback: callback
164
166
  end
165
167
 
166
- def chain_unsubscribe_finalized_heads(subscription)
167
- invoke rpc_method(__method__), subscription
168
+ def chain_unsubscribe_finalized_heads(subscription, &callback)
169
+ invoke rpc_method(__method__), [ subscription ], callback
168
170
  end
169
171
 
170
172
  def state_subscribe_runtime_version(&callback)
171
- request rpc_method(__method__), [], callback
173
+ request rpc_method(__method__), [], subscription_callback: callback
172
174
  end
173
175
 
174
- def state_unsubscribe_runtime_version(subscription)
175
- invoke rpc_method(__method__), subscription
176
+ def state_unsubscribe_runtime_version(subscription, &callback)
177
+ invoke rpc_method(__method__), [ subscription ], callback
176
178
  end
177
179
 
178
180
  def state_subscribe_storage(keys, &callback)
179
- request rpc_method(__method__), [keys], callback
181
+ request rpc_method(__method__), [keys], subscription_callback: callback
180
182
  end
181
183
 
182
- def state_unsubscribe_storage(subscription)
183
- invoke rpc_method(__method__), subscription
184
+ def state_unsubscribe_storage(subscription, &callback)
185
+ invoke rpc_method(__method__), [ subscription ], callback
184
186
  end
185
187
 
186
188
  # ################################################
187
189
  # custom methods based on origin rpc methods
188
190
  # ################################################
189
- def method_list
190
- self.rpc_methods["methods"].map(&:underscore)
191
- end
192
-
193
- def get_block_number(block_hash)
194
- header = self.chain_get_header(block_hash)
195
- header["number"].to_i(16)
191
+ def method_list(&callback)
192
+ self.rpc_methods do |result|
193
+ callback.call result["methods"].map(&:underscore)
194
+ end
196
195
  end
197
196
 
198
- def get_metadata(block_hash)
199
- hex = self.state_get_metadata(block_hash)
200
- Scale::Types::Metadata.decode(Scale::Bytes.new(hex))
197
+ def get_block_number(block_hash, &callback)
198
+ self.chain_get_header(block_hash) do |header|
199
+ callback.call header["number"].to_i(16)
200
+ end
201
201
  end
202
202
 
203
- def get_block(block_hash=nil)
204
- self.init_runtime(block_hash: block_hash)
205
- block = self.chain_get_block(block_hash)
206
-
207
- block["block"]["header"]["number"] = block["block"]["header"]["number"].to_i(16)
208
-
209
- block["block"]["extrinsics"].each_with_index do |hex, i|
210
- scale_bytes = Scale::Bytes.new(hex)
211
- block["block"]["extrinsics"][i] = Scale::Types::Extrinsic.decode(scale_bytes).to_human
203
+ def get_metadata(block_hash=nil, &callback)
204
+ self.state_get_metadata(block_hash) do |hex|
205
+ callback.call Scale::Types::Metadata.decode(Scale::Bytes.new(hex))
212
206
  end
207
+ end
213
208
 
214
- block['block']['header']["digest"]["logs"].each_with_index do |hex, i|
215
- scale_bytes = Scale::Bytes.new(hex)
216
- block['block']['header']["digest"]["logs"][i] = Scale::Types::LogDigest.decode(scale_bytes).to_human
209
+ def get_block(block_hash=nil, &callback)
210
+ self.init_runtime block_hash do
211
+ self.chain_get_block(block_hash) do |block|
212
+ block = Helper.decode_block block
213
+ callback.call block
214
+ end
217
215
  end
218
-
219
- block
220
216
  end
221
217
 
222
- def get_block_events(block_hash)
223
- self.init_runtime(block_hash: block_hash)
224
-
225
- storage_key = "0x26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7"
226
- events_data = state_get_storage storage_key, block_hash
227
-
228
- scale_bytes = Scale::Bytes.new(events_data)
229
- Scale::Types.get("Vec<EventRecord>").decode(scale_bytes).to_human
218
+ def get_block_events(block_hash=nil, &callback)
219
+ self.init_runtime(block_hash) do
220
+ storage_key = "0x26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7"
221
+ self.state_get_storage storage_key, block_hash do |events_data|
222
+ scale_bytes = Scale::Bytes.new(events_data)
223
+ events = Scale::Types.get("Vec<EventRecord>").decode(scale_bytes).to_human
224
+ callback.call events
225
+ end
226
+ end
230
227
  end
231
228
 
232
229
  def subscribe_block_events(&callback)
233
- self.chain_subscribe_finalised_heads do |data|
230
+ self.chain_subscribe_finalized_heads do |data|
234
231
 
235
232
  block_number = data["params"]["result"]["number"].to_i(16) - 1
236
233
  block_hash = data["params"]["result"]["parentHash"]
@@ -238,17 +235,18 @@ class SubstrateClient
238
235
  EM.defer(
239
236
 
240
237
  proc {
241
- events = get_block_events block_hash
242
- { block_number: block_number, events: events }
238
+ self.get_block_events block_hash do |events|
239
+ begin
240
+ result = { block_number: block_number, events: events }
241
+ callback.call result
242
+ rescue => ex
243
+ SubstrateClient.logger.error ex.message
244
+ SubstrateClient.logger.error ex.backtrace.join("\n")
245
+ end
246
+ end
243
247
  },
244
248
 
245
249
  proc { |result|
246
- begin
247
- callback.call result
248
- rescue => ex
249
- SubstrateClient.logger.error ex.message
250
- SubstrateClient.logger.error ex.backtrace.join("\n")
251
- end
252
250
  },
253
251
 
254
252
  proc { |e|
@@ -259,130 +257,25 @@ class SubstrateClient
259
257
  end
260
258
  end
261
259
 
262
- # Plain: client.get_storage("Sudo", "Key")
263
- # Plain: client.get_storage("Balances", "TotalIssuance")
264
- # Map: client.get_storage("System", "Account", ["0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"])
265
- # DoubleMap: client.get_storage("ImOnline", "AuthoredBlocks", [2818, "0x749ddc93a65dfec3af27cc7478212cb7d4b0c0357fef35a0163966ab5333b757"])
266
- def get_storage(module_name, storage_name, params = nil, block_hash = nil)
267
- self.init_runtime(block_hash: block_hash)
268
-
269
- # find the storage item from metadata
270
- metadata_modules = metadata.value.value[:metadata][:modules]
271
- metadata_module = metadata_modules.detect { |mm| mm[:name] == module_name }
272
- raise "Module '#{module_name}' not exist" unless metadata_module
273
- storage_item = metadata_module[:storage][:items].detect { |item| item[:name] == storage_name }
274
- raise "Storage item '#{storage_name}' not exist. \n#{metadata_module.inspect}" unless storage_item
275
-
276
- if storage_item[:type][:Plain]
277
- return_type = storage_item[:type][:Plain]
278
- elsif map = storage_item[:type][:Map]
279
- raise "Storage call of type \"Map\" requires 1 parameter" if params.nil? || params.length != 1
280
-
281
- hasher = map[:hasher]
282
- return_type = map[:value]
283
- # TODO: decode to account id if param is address
284
- # params[0] = decode(params[0]) if map[:key] == "AccountId"
285
- params[0] = Scale::Types.get(map[:key]).new(params[0]).encode
286
- elsif map = storage_item[:type][:DoubleMap]
287
- raise "Storage call of type \"DoubleMapType\" requires 2 parameters" if params.nil? || params.length != 2
288
-
289
- hasher = map[:hasher]
290
- hasher2 = map[:key2Hasher]
291
- return_type = map[:value]
292
- params[0] = Scale::Types.get(map[:key1]).new(params[0]).encode
293
- params[1] = Scale::Types.get(map[:key2]).new(params[1]).encode
294
- else
295
- raise NotImplementedError
296
- end
297
-
298
- storage_hash = SubstrateClient.generate_storage_hash(
299
- module_name,
300
- storage_name,
301
- params,
302
- hasher,
303
- hasher2,
304
- metadata.value.value[:metadata][:version]
305
- )
306
-
307
- p module_name
308
- p storage_name
309
- p params
310
- p hasher
311
- p hasher2
312
- p metadata.value.value[:metadata][:version]
313
- result = self.state_get_storage(storage_hash, block_hash)
314
- return unless result
315
- Scale::Types.get(return_type).decode(Scale::Bytes.new(result))
316
- end
317
-
318
- # compose_call "Balances", "Transfer", { dest: "0x586cb27c291c813ce74e86a60dad270609abf2fc8bee107e44a80ac00225c409", value: 1_000_000_000_000 }
319
- def compose_call(module_name, call_name, params, block_hash=nil)
320
- self.init_runtime(block_hash: block_hash)
321
-
322
- call = metadata.get_module_call(module_name, call_name)
323
-
324
- value = {
325
- call_index: call[:lookup],
326
- module_name: module_name,
327
- call_name: call_name,
328
- params: []
329
- }
330
-
331
- params.keys.each_with_index do |call_param_name, i|
332
- param_value = params[call_param_name]
333
- value[:params] << {
334
- name: call_param_name.to_s,
335
- type: call[:args][i][:type],
336
- value: param_value
337
- }
338
- end
339
-
340
- Scale::Types::Extrinsic.new(value).encode
341
- end
342
-
343
- class << self
344
- def generate_storage_hash(module_name, storage_name, params = nil, hasher = nil, hasher2 = nil, metadata_version = nil)
345
- if metadata_version and metadata_version >= 9
346
- storage_hash = Crypto.twox128(module_name) + Crypto.twox128(storage_name)
347
-
348
- params&.each_with_index do |param, index|
349
- if index == 0
350
- param_hasher = hasher
351
- elsif index == 1
352
- param_hasher = hasher2
353
- else
354
- raise "Unexpected third parameter for storage call"
355
- end
356
-
357
- param_key = param.hex_to_bytes
358
- param_hasher = "Twox128" if param_hasher.nil?
359
- storage_hash += Crypto.send param_hasher.underscore, param_key
360
- end
361
-
362
- "0x#{storage_hash}"
363
- else
364
- # TODO: add test
365
- storage_hash = module_name + " " + storage_name
366
-
367
- unless params.nil?
368
- params = [params] if params.class != ::Array
369
- params_key = params.join("")
370
- hasher = "Twox128" if hasher.nil?
371
- storage_hash += params_key.hex_to_bytes.bytes_to_utf8
260
+ def get_storage(module_name, storage_name, params = nil, block_hash = nil, &callback)
261
+ self.init_runtime(block_hash) do
262
+ storage_hash, return_type = Helper.generate_storage_hash_from_metadata(@metadata, module_name, storage_name, params)
263
+ self.state_get_storage(storage_hash, block_hash) do |result|
264
+ if result
265
+ storage = Scale::Types.get(return_type).decode(Scale::Bytes.new(result))
266
+ callback.call storage
267
+ else
268
+ callback.call nil
372
269
  end
373
-
374
- "0x#{Crypto.send( hasher.underscore, storage_hash )}"
375
270
  end
376
271
  end
272
+ end
377
273
 
378
- # chain_unsubscribe_runtime_version
379
- # =>
380
- # chain_unsubscribeRuntimeVersion
381
- def real_method_name(method_name)
382
- segments = method_name.to_s.split("_")
383
- segments[0] + "_" + segments[1] + segments[2..].map(&:capitalize).join
274
+ def compose_call(module_name, call_name, params, block_hash=nil, &callback)
275
+ self.init_runtime(block_hash) do
276
+ hex = Helper.compose_call_from_metadata(@metadata, module_name, call_name, params)
277
+ callback.call hex
384
278
  end
385
-
386
279
  end
387
280
 
388
281
  private
@@ -1,3 +1,3 @@
1
1
  class SubstrateClient
2
- VERSION = "0.1.5"
2
+ VERSION = "0.1.6"
3
3
  end
@@ -0,0 +1,190 @@
1
+ def ws_request(url, payload)
2
+ queue = TimeoutQueue.new
3
+
4
+ thread = Thread.new do
5
+ EM.run do
6
+ ws = Faye::WebSocket::Client.new(url)
7
+
8
+ ws.on :open do |event|
9
+ ws.send(payload.to_json)
10
+ end
11
+
12
+ ws.on :message do |event|
13
+ if event.data.include?("jsonrpc")
14
+ queue << JSON.parse(event.data)
15
+ ws.close(3001, "data received")
16
+ Thread.kill thread
17
+ end
18
+ end
19
+
20
+ ws.on :close do |event|
21
+ ws = nil
22
+ end
23
+ end
24
+ end
25
+
26
+ queue.pop true, 10
27
+ rescue ThreadError => ex
28
+ raise SubstrateClientSync::RpcTimeout
29
+ end
30
+
31
+ class SubstrateClientSync
32
+ class RpcError < StandardError; end
33
+ class RpcTimeout < StandardError; end
34
+
35
+ attr_accessor :spec_name, :spec_version, :metadata
36
+
37
+ def initialize(url, spec_name: nil)
38
+ @url = url
39
+ @request_id = 1
40
+ @spec_name = spec_name
41
+ Scale::TypeRegistry.instance.load(spec_name)
42
+ end
43
+
44
+ def request(method, params)
45
+ payload = {
46
+ "jsonrpc" => "2.0",
47
+ "method" => method,
48
+ "params" => params,
49
+ "id" => @request_id
50
+ }
51
+
52
+ data = ws_request(@url, payload)
53
+ if data["error"]
54
+ raise RpcError, data["error"]
55
+ else
56
+ data["result"]
57
+ end
58
+ end
59
+
60
+ def init_runtime(block_hash=nil)
61
+ # set current runtime spec version
62
+ runtime_version = self.state_get_runtime_version(block_hash)
63
+ @spec_version = runtime_version["specVersion"]
64
+ Scale::TypeRegistry.instance.spec_version = @spec_version
65
+
66
+ # set current metadata
67
+ @metadata = self.get_metadata(block_hash)
68
+ Scale::TypeRegistry.instance.metadata = @metadata.value
69
+ true
70
+ end
71
+
72
+ def invoke(method, *params)
73
+ # params.reject! { |param| param.nil? }
74
+ request(method, params)
75
+ end
76
+
77
+ def rpc_method(method_name)
78
+ SubstrateClient::Helper.real_method_name(method_name.to_s)
79
+ end
80
+
81
+ # ################################################
82
+ # origin rpc methods
83
+ # ################################################
84
+ def method_missing(method, *args)
85
+ rpc_method = SubstrateClient::Helper.real_method_name(method)
86
+ invoke rpc_method, *args
87
+ end
88
+
89
+ def state_get_runtime_version(block_hash=nil)
90
+ invoke rpc_method(__method__), block_hash
91
+ end
92
+
93
+ def rpc_methods
94
+ invoke rpc_method(__method__)
95
+ end
96
+
97
+ def chain_get_head
98
+ invoke rpc_method(__method__)
99
+ end
100
+
101
+ def chain_get_finalised_head
102
+ invoke rpc_method(__method__)
103
+ end
104
+
105
+ def chain_get_header(block_hash = nil)
106
+ invoke rpc_method(__method__), block_hash
107
+ end
108
+
109
+ def chain_get_block(block_hash = nil)
110
+ invoke rpc_method(__method__), block_hash
111
+ end
112
+
113
+ def chain_get_block_hash(block_id)
114
+ invoke rpc_method(__method__), block_id
115
+ end
116
+
117
+ def chain_get_runtime_version(block_hash = nil)
118
+ invoke rpc_method(__method__), block_hash
119
+ end
120
+
121
+ def state_get_metadata(block_hash = nil)
122
+ invoke rpc_method(__method__), block_hash
123
+ end
124
+
125
+ def state_get_storage(storage_key, block_hash = nil)
126
+ invoke rpc_method(__method__), storage_key, block_hash
127
+ end
128
+
129
+ def system_name
130
+ invoke rpc_method(__method__)
131
+ end
132
+
133
+ def system_version
134
+ invoke rpc_method(__method__)
135
+ end
136
+
137
+ # ################################################
138
+ # custom methods based on origin rpc methods
139
+ # ################################################
140
+ def method_list
141
+ self.rpc_methods["methods"].map(&:underscore)
142
+ end
143
+
144
+ def get_block_number(block_hash)
145
+ header = self.chain_get_header(block_hash)
146
+ header["number"].to_i(16)
147
+ end
148
+
149
+ def get_metadata(block_hash=nil)
150
+ hex = self.state_get_metadata(block_hash)
151
+ Scale::Types::Metadata.decode(Scale::Bytes.new(hex))
152
+ end
153
+
154
+ def get_block(block_hash=nil)
155
+ self.init_runtime(block_hash)
156
+ block = self.chain_get_block(block_hash)
157
+ SubstrateClient::Helper.decode_block(block)
158
+ end
159
+
160
+ def get_block_events(block_hash=nil)
161
+ self.init_runtime(block_hash)
162
+
163
+ storage_key = "0x26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7"
164
+ events_data = state_get_storage storage_key, block_hash
165
+
166
+ scale_bytes = Scale::Bytes.new(events_data)
167
+ Scale::Types.get("Vec<EventRecord>").decode(scale_bytes).to_human
168
+ end
169
+
170
+ # Plain: client.get_storage("Sudo", "Key")
171
+ # Plain: client.get_storage("Balances", "TotalIssuance")
172
+ # Map: client.get_storage("System", "Account", ["0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"])
173
+ # DoubleMap: client.get_storage("ImOnline", "AuthoredBlocks", [2818, "0x749ddc93a65dfec3af27cc7478212cb7d4b0c0357fef35a0163966ab5333b757"])
174
+ def get_storage(module_name, storage_name, params = nil, block_hash = nil)
175
+ self.init_runtime(block_hash)
176
+
177
+ storage_hash, return_type = SubstrateClient::Helper.generate_storage_hash_from_metadata(@metadata, module_name, storage_name, params)
178
+
179
+ result = self.state_get_storage(storage_hash, block_hash)
180
+ return unless result
181
+ Scale::Types.get(return_type).decode(Scale::Bytes.new(result))
182
+ end
183
+
184
+ # compose_call "Balances", "Transfer", { dest: "0x586cb27c291c813ce74e86a60dad270609abf2fc8bee107e44a80ac00225c409", value: 1_000_000_000_000 }
185
+ def compose_call(module_name, call_name, params, block_hash=nil)
186
+ self.init_runtime(block_hash)
187
+ SubstrateClient::Helper.compose_call_from_metadata(@metadata, module_name, call_name, params)
188
+ end
189
+
190
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: substrate_client.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.1.6
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-19 00:00:00.000000000 Z
11
+ date: 2020-05-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faye-websocket
@@ -142,8 +142,10 @@ files:
142
142
  - bin/console
143
143
  - bin/setup
144
144
  - exe/metadata
145
+ - lib/helper.rb
145
146
  - lib/substrate_client.rb
146
147
  - lib/substrate_client/version.rb
148
+ - lib/substrate_client_sync.rb
147
149
  - lib/timeout_queue.rb
148
150
  - lib/websocket.rb
149
151
  - substrate_client.gemspec