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