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,3 +1,3 @@
1
1
  class SubstrateClient
2
- VERSION = "0.1.2"
2
+ VERSION = "0.1.7"
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
@@ -0,0 +1,34 @@
1
+ # https://vaneyckt.io/posts/ruby_concurrency_building_a_timeout_queue/
2
+ class TimeoutQueue
3
+ def initialize
4
+ @elems = []
5
+ @mutex = Mutex.new
6
+ @cond_var = ConditionVariable.new
7
+ end
8
+
9
+ def <<(elem)
10
+ @mutex.synchronize do
11
+ @elems << elem
12
+ @cond_var.signal
13
+ end
14
+ end
15
+
16
+ def pop(blocking = true, timeout = nil)
17
+ @mutex.synchronize do
18
+ if blocking
19
+ if timeout.nil?
20
+ while @elems.empty?
21
+ @cond_var.wait(@mutex)
22
+ end
23
+ else
24
+ timeout_time = Time.now.to_f + timeout
25
+ while @elems.empty? && (remaining_time = timeout_time - Time.now.to_f) > 0
26
+ @cond_var.wait(@mutex, remaining_time)
27
+ end
28
+ end
29
+ end
30
+ raise ThreadError, 'queue empty' if @elems.empty?
31
+ @elems.shift
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,79 @@
1
+ require "faye/websocket"
2
+ require "eventmachine"
3
+
4
+ class SubstrateClient::Websocket
5
+ HEARTBEAT_INTERVAL = 3
6
+ RECONNECT_INTERVAL = 3
7
+
8
+ def initialize(url, onopen: nil, onmessage: nil)
9
+ @url = url
10
+ @onopen = onopen || proc { p [:open] }
11
+ @onmessage = onmessage || proc { |event| p [:message, event.data] }
12
+
13
+ @thread = Thread.new do
14
+ EM.run do
15
+ start_connection
16
+ end
17
+ SubstrateClient.logger.info "Event loop stopped"
18
+ end
19
+ @heartbeat_thread = start_heartbeat
20
+ end
21
+
22
+ def start_connection
23
+ SubstrateClient.logger.info "Start to connect"
24
+ @close = false
25
+ @missed_heartbeats = 0
26
+ @ping_id = 0
27
+ @ws = Faye::WebSocket::Client.new(@url)
28
+ @ws.on :open do |event|
29
+ @do_heartbeat = true
30
+ @onopen.call event
31
+ end
32
+
33
+ @ws.on :message do |event|
34
+ @onmessage.call event
35
+ end
36
+
37
+ @ws.on :close do |event|
38
+ # p [:close, event.code, event.reason]
39
+ if @close == false
40
+ @do_heartbeat = false
41
+ sleep RECONNECT_INTERVAL
42
+ start_connection
43
+ end
44
+ end
45
+
46
+ end
47
+
48
+ def start_heartbeat
49
+ Thread.new do
50
+ loop do
51
+ send_heartbeat if @do_heartbeat
52
+ sleep HEARTBEAT_INTERVAL
53
+ end
54
+ end
55
+ end
56
+
57
+ def send_heartbeat
58
+ if @missed_heartbeats < 2
59
+ # puts "ping_#{@ping_id}"
60
+ @ws.ping @ping_id.to_s do
61
+ # puts "pong"
62
+ @missed_heartbeats -= 1
63
+ end
64
+ @missed_heartbeats += 1
65
+ @ping_id += 1
66
+ end
67
+ end
68
+
69
+ def send(message)
70
+ @ws.send message
71
+ end
72
+
73
+ def close
74
+ @close = true
75
+ Thread.kill @heartbeat_thread
76
+ Thread.kill @thread
77
+ @ws = nil
78
+ end
79
+ end
@@ -36,11 +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 "substrate_common.rb", "~> 0.1.8"
40
39
  spec.add_dependency "faye-websocket", "~> 0.10.9"
41
40
  spec.add_dependency "eventmachine", "~> 1.2.7"
42
41
  spec.add_dependency "activesupport", "~> 5.2.4"
43
- spec.add_dependency "scale.rb", "~> 0.2.1"
42
+ spec.add_dependency "scale.rb", "~> 0.2.5"
44
43
 
45
44
  spec.add_development_dependency "bundler", "~> 1.17"
46
45
  spec.add_development_dependency "rake", ">= 12.3.3"
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: substrate_client.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.7
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-04-08 00:00:00.000000000 Z
11
+ date: 2020-06-11 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: substrate_common.rb
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: 0.1.8
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: 0.1.8
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: faye-websocket
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -72,14 +58,14 @@ dependencies:
72
58
  requirements:
73
59
  - - "~>"
74
60
  - !ruby/object:Gem::Version
75
- version: 0.2.1
61
+ version: 0.2.5
76
62
  type: :runtime
77
63
  prerelease: false
78
64
  version_requirements: !ruby/object:Gem::Requirement
79
65
  requirements:
80
66
  - - "~>"
81
67
  - !ruby/object:Gem::Version
82
- version: 0.2.1
68
+ version: 0.2.5
83
69
  - !ruby/object:Gem::Dependency
84
70
  name: bundler
85
71
  requirement: !ruby/object:Gem::Requirement
@@ -141,7 +127,6 @@ email:
141
127
  - wuminzhe@gmail.com
142
128
  executables:
143
129
  - metadata
144
- - substrate_client
145
130
  extensions: []
146
131
  extra_rdoc_files: []
147
132
  files:
@@ -149,6 +134,7 @@ files:
149
134
  - ".rspec"
150
135
  - ".travis.yml"
151
136
  - CODE_OF_CONDUCT.md
137
+ - Dockerfile
152
138
  - Gemfile
153
139
  - Gemfile.lock
154
140
  - LICENSE.txt
@@ -157,9 +143,12 @@ files:
157
143
  - bin/console
158
144
  - bin/setup
159
145
  - exe/metadata
160
- - exe/substrate_client
146
+ - lib/helper.rb
161
147
  - lib/substrate_client.rb
162
148
  - lib/substrate_client/version.rb
149
+ - lib/substrate_client_sync.rb
150
+ - lib/timeout_queue.rb
151
+ - lib/websocket.rb
163
152
  - substrate_client.gemspec
164
153
  homepage: https://github.com/itering/substrate_client.rb
165
154
  licenses:
@@ -1,18 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "substrate_client"
4
- require "json"
5
-
6
- client = SubstrateClient.new("wss://cc3-5.kusama.network/")
7
- # puts client.system_name
8
- # puts client.system_chain
9
- # puts client.system_version
10
- # puts client.system_peers
11
- # puts client.state_getStorage "0x0b76934f4cc08dee01012d059e1b83ee5e0621c4869aa60c02be9adcc98a0d1d"
12
- # puts client.state_getChildKeys "0x","0x",1,"0x"
13
- # puts client.runtime_getState "System", "Events",[],"0xe0303fbe2bc8482e06fb649f934c41349d12a25bd618c92e3331a3347e434f7b"
14
- # puts client.rpc_methods
15
-
16
- client.init
17
- metadata = client.metadata
18
- puts JSON.pretty_generate(metadata)