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.
- checksums.yaml +4 -4
- data/Dockerfile +17 -0
- data/Gemfile.lock +5 -6
- data/README.md +240 -9
- data/exe/metadata +6 -6
- data/lib/helper.rb +129 -0
- data/lib/substrate_client.rb +274 -153
- data/lib/substrate_client/version.rb +1 -1
- data/lib/substrate_client_sync.rb +190 -0
- data/lib/timeout_queue.rb +34 -0
- data/lib/websocket.rb +79 -0
- data/substrate_client.gemspec +1 -2
- metadata +9 -20
- data/exe/substrate_client +0 -18
@@ -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
|
data/lib/websocket.rb
ADDED
@@ -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
|
data/substrate_client.gemspec
CHANGED
@@ -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.
|
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.
|
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-
|
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.
|
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.
|
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
|
-
-
|
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:
|
data/exe/substrate_client
DELETED
@@ -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)
|