substrate_client.rb 0.1.2 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,198 +1,319 @@
1
1
  require "substrate_client/version"
2
2
 
3
- require "substrate_common"
4
- require "scale"
5
-
6
- require "faye/websocket"
7
- require "eventmachine"
3
+ require "logger"
4
+ require "scale.rb"
8
5
  require "json"
9
6
  require "active_support"
10
7
  require "active_support/core_ext/string"
8
+ require "websocket"
9
+ require "helper"
10
+ require "timeout_queue"
11
+ require "substrate_client_sync"
11
12
 
12
- def ws_request(url, payload)
13
- result = nil
14
-
15
- EM.run do
16
- ws = Faye::WebSocket::Client.new(url)
17
-
18
- ws.on :open do |event|
19
- # p [:open]
20
- ws.send(payload.to_json)
21
- end
22
-
23
- ws.on :message do |event|
24
- # p [:message, event.data]
25
- if event.data.include?("jsonrpc")
26
- result = JSON.parse event.data
27
- ws.close(3001, "data received")
28
- EM.stop
29
- end
30
- end
31
-
32
- ws.on :close do |event|
33
- # p [:close, event.code, event.reason]
34
- ws = nil
35
- end
13
+ class SubstrateClient
14
+ class RpcError < StandardError; end
15
+ class RpcTimeout < StandardError; end
16
+ class << self
17
+ attr_accessor :logger
36
18
  end
19
+ SubstrateClient.logger = Logger.new(STDOUT)
20
+ SubstrateClient.logger.level = Logger::INFO
37
21
 
38
- result
39
- end
40
-
41
- class SubstrateClient
42
22
  attr_accessor :spec_name, :spec_version, :metadata
23
+ attr_accessor :ws
43
24
 
44
- def initialize(url)
25
+ def initialize(url, spec_name: nil, onopen: nil)
45
26
  @url = url
46
27
  @request_id = 1
28
+ @spec_name = spec_name
29
+ @onopen = onopen
30
+ Scale::TypeRegistry.instance.load(spec_name)
31
+
32
+ init_ws
33
+
34
+ at_exit { self.close }
47
35
  end
48
36
 
49
- # TODO: error
50
- def request(method, params)
37
+ def close
38
+ @ws.close
39
+ end
40
+
41
+ def request(method, params, callback: nil, subscription_callback: nil)
51
42
  payload = {
52
43
  "jsonrpc" => "2.0",
53
44
  "method" => method,
54
45
  "params" => params,
55
46
  "id" => @request_id
56
47
  }
48
+
49
+ while @callbacks.nil?
50
+ sleep(1)
51
+ end
52
+
53
+ @callbacks[@request_id] = proc do |data|
54
+ if not subscription_callback.nil? && data["result"]
55
+ @subscription_callbacks[data["result"]] = subscription_callback
56
+ end
57
+
58
+ callback.call data if callback
59
+ end
60
+ @ws.send(payload.to_json)
57
61
  @request_id += 1
58
- ws_request(@url, payload)
59
62
  end
60
63
 
61
- # ############################
62
- # native rpc methods support
63
- # ############################
64
- def method_missing(method, *args)
65
- data = request(SubstrateClient.real_method_name(method), args)
66
- data["result"]
64
+ def init_runtime(block_hash=nil, &callback)
65
+ # set current runtime spec version
66
+ self.state_get_runtime_version(block_hash) do |runtime_version|
67
+ @spec_version = runtime_version["specVersion"]
68
+ Scale::TypeRegistry.instance.spec_version = @spec_version
69
+
70
+ # set current metadata
71
+ self.get_metadata(block_hash) do |metadata|
72
+ @metadata = metadata
73
+ Scale::TypeRegistry.instance.metadata = @metadata.value
74
+ callback.call
75
+ end
76
+ end
77
+ end
78
+
79
+ def do_init_runtime(block_hash, &callback)
80
+ end
81
+
82
+ def invoke(method, params, callback)
83
+ request method, params, callback: proc { |data|
84
+ if data["error"]
85
+ Thread.main.raise RpcError, data["error"]
86
+ else
87
+ callback.call data["result"] unless callback.nil?
88
+ end
89
+ }
90
+ end
91
+
92
+ def rpc_method(method_name)
93
+ Helper.real_method_name(method_name.to_s)
67
94
  end
68
95
 
69
96
  # ################################################
70
- # custom methods wrapped from native rpc methods
97
+ # origin rpc methods
71
98
  # ################################################
72
- def method_list
73
- methods = self.rpc_methods["methods"].map(&:underscore)
74
- methods << "method_list"
75
- methods << "get_storage_at"
76
- end
77
-
78
- def init(block_hash = nil)
79
- block_runtime_version = self.state_get_runtime_version(block_hash)
80
- @spec_name = block_runtime_version["specName"]
81
- @spec_version = block_runtime_version["specVersion"]
82
-
83
- Scale::TypeRegistry.instance.load(spec_name, spec_version)
84
- @metadata = self.get_metadata(block_hash)
85
- true
86
- end
87
-
88
- def get_metadata(block_hash)
89
- hex = self.state_get_metadata(block_hash)
90
- metadata = Scale::Types::Metadata.decode(Scale::Bytes.new(hex))
91
- metadata.value.value[:metadata]
92
- end
93
-
94
- # client.init(0x014e4248dd04a8c0342b603a66df0691361ac58e69595e248219afa7af87bdc7)
95
- # Plain: client.get_storage_at("Balances", "TotalIssuance")
96
- # Map: client.get_storage_at("System", "Account", ["0x30599dba50b5f3ba0b36f856a761eb3c0aee61e830d4beb448ef94b6ad92be39"])
97
- # DoubleMap: client.get_storage_at("ImOnline", "AuthoredBlocks", [2818, "0x749ddc93a65dfec3af27cc7478212cb7d4b0c0357fef35a0163966ab5333b757"])
98
- def get_storage_at(module_name, storage_function_name, params = nil)
99
-
100
- # TODO: uninit raise a exception
101
- # find the storage item from metadata
102
- metadata_modules = metadata[:modules]
103
- metadata_module = metadata_modules.detect { |mm| mm[:name] == module_name }
104
- raise "Module '#{module_name}' not exist" unless metadata_module
105
- storage_item = metadata_module[:storage][:items].detect { |item| item[:name] == storage_function_name }
106
- raise "Storage item '#{storage_function_name}' not exist. \n#{metadata_module.inspect}" unless storage_item
107
-
108
- if storage_item[:type][:Plain]
109
- return_type = storage_item[:type][:Plain]
110
- elsif map = storage_item[:type][:Map]
111
- raise "Storage call of type \"Map\" requires 1 parameter" if params.nil? || params.length != 1
112
-
113
- hasher = map[:hasher]
114
- return_type = map[:value]
115
- # TODO: decode to account id if param is address
116
- # params[0] = decode(params[0]) if map[:key] == "AccountId"
117
- params[0] = Scale::Types.get(map[:key]).new(params[0]).encode
118
- elsif map = storage_item[:type][:DoubleMap]
119
- raise "Storage call of type \"DoubleMapType\" requires 2 parameters" if params.nil? || params.length != 2
120
-
121
- hasher = map[:hasher]
122
- hasher2 = map[:key2Hasher]
123
- return_type = map[:value]
124
- params[0] = Scale::Types.get(map[:key1]).new(params[0]).encode
125
- params[1] = Scale::Types.get(map[:key2]).new(params[1]).encode
126
- else
127
- raise NotImplementedError
99
+ def method_missing(method, args, &callback)
100
+ rpc_method = Helper.real_method_name(method)
101
+ invoke rpc_method, args, callback
102
+ end
103
+
104
+ def state_get_runtime_version(block_hash=nil, &callback)
105
+ invoke rpc_method(__method__), [ block_hash ], callback
106
+ end
107
+
108
+ def rpc_methods(&callback)
109
+ invoke rpc_method(__method__), [], callback
110
+ end
111
+
112
+ def chain_get_head(&callback)
113
+ invoke rpc_method(__method__), [], callback
114
+ end
115
+
116
+ def chain_get_finalised_head(&callback)
117
+ invoke rpc_method(__method__), [], callback
118
+ end
119
+
120
+ def chain_get_header(block_hash = nil, &callback)
121
+ invoke rpc_method(__method__), [ block_hash ], callback
122
+ end
123
+
124
+ def chain_get_block(block_hash = nil, &callback)
125
+ invoke rpc_method(__method__), [ block_hash ], callback
126
+ end
127
+
128
+ def chain_get_block_hash(block_id, &callback)
129
+ invoke rpc_method(__method__), [ block_id ], callback
130
+ end
131
+
132
+ def chain_get_runtime_version(block_hash = nil, &callback)
133
+ invoke rpc_method(__method__), [ block_hash ], callback
134
+ end
135
+
136
+ def state_get_metadata(block_hash = nil, &callback)
137
+ invoke rpc_method(__method__), [ block_hash ], callback
138
+ end
139
+
140
+ def state_get_storage(storage_key, block_hash = nil, &callback)
141
+ invoke rpc_method(__method__), [ storage_key, block_hash ], callback
142
+ end
143
+
144
+ def system_name(&callback)
145
+ invoke rpc_method(__method__), [], callback
146
+ end
147
+
148
+ def system_version(&callback)
149
+ invoke rpc_method(__method__), [], callback
150
+ end
151
+
152
+ def chain_subscribe_all_heads(&callback)
153
+ request rpc_method(__method__), [], subscription_callback: callback
154
+ end
155
+
156
+ def chain_unsubscribe_all_heads(subscription)
157
+ invoke rpc_method(__method__), [ subscription ], nil
158
+ end
159
+
160
+ def chain_subscribe_new_heads(&callback)
161
+ request rpc_method(__method__), [], subscription_callback: callback
162
+ end
163
+
164
+ def chain_unsubscribe_new_heads(subscription)
165
+ invoke rpc_method(__method__), [ subscription ], nil
166
+ end
167
+
168
+ def chain_subscribe_finalized_heads(&callback)
169
+ request rpc_method(__method__), [], subscription_callback: callback
170
+ end
171
+
172
+ def chain_unsubscribe_finalized_heads(subscription)
173
+ invoke rpc_method(__method__), [ subscription ], nil
174
+ end
175
+
176
+ def state_subscribe_runtime_version(&callback)
177
+ request rpc_method(__method__), [], subscription_callback: callback
178
+ end
179
+
180
+ def state_unsubscribe_runtime_version(subscription)
181
+ invoke rpc_method(__method__), [ subscription ], nil
182
+ end
183
+
184
+ def state_subscribe_storage(keys, &callback)
185
+ request rpc_method(__method__), [keys], subscription_callback: callback
186
+ end
187
+
188
+ def state_unsubscribe_storage(subscription)
189
+ invoke rpc_method(__method__), [ subscription ], nil
190
+ end
191
+
192
+ # ################################################
193
+ # custom methods based on origin rpc methods
194
+ # ################################################
195
+ def method_list(&callback)
196
+ self.rpc_methods do |result|
197
+ callback.call result["methods"].map(&:underscore)
128
198
  end
199
+ end
129
200
 
130
- storage_hash = SubstrateClient.generate_storage_hash(
131
- module_name,
132
- storage_function_name,
133
- params,
134
- hasher,
135
- hasher2,
136
- metadata[:version]
137
- )
201
+ def get_block_number(block_hash, &callback)
202
+ self.chain_get_header(block_hash) do |header|
203
+ callback.call header["number"].to_i(16)
204
+ end
205
+ end
138
206
 
139
- # puts storage_hash
207
+ def get_metadata(block_hash=nil, &callback)
208
+ self.state_get_metadata(block_hash) do |hex|
209
+ callback.call Scale::Types::Metadata.decode(Scale::Bytes.new(hex))
210
+ end
211
+ end
212
+
213
+ def get_block(block_hash=nil, &callback)
214
+ self.init_runtime block_hash do
215
+ self.chain_get_block(block_hash) do |block|
216
+ block = Helper.decode_block block
217
+ callback.call block
218
+ end
219
+ end
220
+ end
140
221
 
141
- result = self.state_get_storage_at(storage_hash, block_hash)
142
- return unless result
143
- Scale::Types.get(return_type).decode(Scale::Bytes.new(result)).value
144
- rescue => ex
145
- puts ex.message
146
- puts ex.backtrace
222
+ def get_block_events(block_hash=nil, &callback)
223
+ self.init_runtime(block_hash) do
224
+ storage_key = "0x26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7"
225
+ self.state_get_storage storage_key, block_hash do |events_data|
226
+ scale_bytes = Scale::Bytes.new(events_data)
227
+ events = Scale::Types.get("Vec<EventRecord>").decode(scale_bytes).to_human
228
+ callback.call events
229
+ end
230
+ end
147
231
  end
148
232
 
149
- class << self
150
- def generate_storage_hash(storage_module_name, storage_function_name, params = nil, hasher = nil, hasher2 = nil, metadata_version = nil)
151
- if metadata_version and metadata_version >= 9
152
- storage_hash = Crypto.twox128(storage_module_name) + Crypto.twox128(storage_function_name)
153
-
154
- if params
155
- params.each_with_index do |param, index|
156
- if index == 0
157
- param_hasher = hasher
158
- elsif index == 1
159
- param_hasher = hasher2
160
- else
161
- raise "Unexpected third parameter for storage call"
162
- end
233
+ def subscribe_block_events(&callback)
234
+ self.chain_subscribe_finalized_heads do |data|
235
+
236
+ block_number = data["params"]["result"]["number"].to_i(16) - 1
237
+ block_hash = data["params"]["result"]["parentHash"]
163
238
 
164
- param_key = param.hex_to_bytes
165
- param_hasher = "Twox128" if param_hasher.nil?
166
- storage_hash += Crypto.send param_hasher.underscore, param_key
239
+ EM.defer(
240
+
241
+ proc {
242
+ self.get_block_events block_hash do |events|
243
+ begin
244
+ result = { block_number: block_number, events: events }
245
+ callback.call result
246
+ rescue => ex
247
+ SubstrateClient.logger.error ex.message
248
+ SubstrateClient.logger.error ex.backtrace.join("\n")
249
+ end
167
250
  end
168
- end
251
+ },
169
252
 
170
- "0x#{storage_hash}"
171
- else
172
- # TODO: add test
173
- storage_hash = storage_module_name + " " + storage_function_name
174
-
175
- unless params.nil?
176
- params = [params] if params.class != ::Array
177
- params_key = params.join("")
178
- hasher = "Twox128" if hasher.nil?
179
- storage_hash += params_key.hex_to_bytes.bytes_to_utf8
180
- end
253
+ proc { |result|
254
+ },
181
255
 
182
- "0x#{Crypto.send( hasher.underscore, storage_hash )}"
183
- end
256
+ proc { |e|
257
+ SubstrateClient.logger.error e
258
+ }
259
+
260
+ )
184
261
  end
262
+ end
185
263
 
186
- # chain_unsubscribe_runtime_version
187
- # =>
188
- # chain_unsubscribeRuntimeVersion
189
- def real_method_name(method_name)
190
- segments = method_name.to_s.split("_")
191
- segments[0] + "_" + segments[1] + segments[2..].map(&:capitalize).join
264
+ def get_storage(module_name, storage_name, params = nil, block_hash = nil, &callback)
265
+ self.init_runtime(block_hash) do
266
+ storage_hash, return_type = Helper.generate_storage_hash_from_metadata(@metadata, module_name, storage_name, params)
267
+ self.state_get_storage(storage_hash, block_hash) do |result|
268
+ if result
269
+ storage = Scale::Types.get(return_type).decode(Scale::Bytes.new(result))
270
+ callback.call storage
271
+ else
272
+ callback.call nil
273
+ end
274
+ end
192
275
  end
276
+ end
193
277
 
278
+ def compose_call(module_name, call_name, params, block_hash=nil, &callback)
279
+ self.init_runtime(block_hash) do
280
+ hex = Helper.compose_call_from_metadata(@metadata, module_name, call_name, params)
281
+ callback.call hex
282
+ end
194
283
  end
195
284
 
285
+ private
286
+ def init_ws
287
+ @ws = Websocket.new(@url,
288
+
289
+ onopen: proc do |event|
290
+ @callbacks = {}
291
+ @subscription_callbacks = {}
292
+ @onopen.call event if not @onopen.nil?
293
+ end,
294
+
295
+ onmessage: proc do |event|
296
+ if event.data.include?("jsonrpc")
297
+ begin
298
+ data = JSON.parse event.data
299
+
300
+ if data["params"]
301
+ if @subscription_callbacks[data["params"]["subscription"]]
302
+ @subscription_callbacks[data["params"]["subscription"]].call data
303
+ end
304
+ else
305
+ @callbacks[data["id"]].call data
306
+ @callbacks.delete(data["id"])
307
+ end
196
308
 
197
- end
309
+ rescue => ex
310
+ SubstrateClient.logger.error ex.message
311
+ SubstrateClient.logger.error ex.backtrace.join("\n")
312
+ end
313
+ end
314
+ end
198
315
 
316
+ )
317
+ end
318
+
319
+ end