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,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)