substrate_client.rb 0.1.2 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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