substrate_client.rb 0.1.3 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5248291da5b3db7dcdcdf913842fceef276c31481346f6da8141960e7d81afdb
4
- data.tar.gz: ffafc99e20d6321c4ab9c6231a3bf706ade3cbb17ba0c043c4401fbf45769c76
3
+ metadata.gz: bb233970cdc1b51057d436d0ffc2ae411b99ce3c2e5c51ba3dfb5dcdae0fc960
4
+ data.tar.gz: 35750c213d2fbf7ae223cdd7b9f7a8bc40c7c7b8fab6a09603a0604db6533562
5
5
  SHA512:
6
- metadata.gz: bb06f911fe64f6e7e6425bfb4921427b5b49aabab86dfa57c4b2d48aeb2a2e057bfb33256762d316be30ff87f11d21a3cd89efd25c7d78985cc7cdf001038fb6
7
- data.tar.gz: e3199b2b0b72423984d7d2baa6160d5c17565b3fd5563375e59661a246c7ba1b80c7ffc82b963cc836e4486f0a48c6f1d070d7ebc48a9803a630b8c04f4f4a7b
6
+ metadata.gz: 5731212fc4ad5791bb607f132e660b89f7ff652937029e986040aeee85335939d5a5edf9dd9a21a91f346c2b88711657586747a01727f35eae8318b9b944a62f
7
+ data.tar.gz: 69514b6b9b3eb085f7fefc672dff0b3a85cdfb64babfad28f6ab788f98ee950e2a2e9fcf2e89ab448b7e8c7137da20ba6ec0e68d375ece1b9c41e5f77d3d6964
@@ -0,0 +1,17 @@
1
+ FROM ruby:2.6-alpine3.11
2
+
3
+ ENV BUILD_PACKAGES curl-dev build-base
4
+
5
+ RUN apk update && \
6
+ apk upgrade && \
7
+ apk add git curl $BUILD_PACKAGES
8
+
9
+ WORKDIR /usr/src/app
10
+
11
+ COPY . .
12
+
13
+ RUN gem install bundler:1.17.3 && \
14
+ bundle install && \
15
+ rake install:local
16
+
17
+ CMD ["sh"]
@@ -1,17 +1,16 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- substrate_client.rb (0.1.3)
4
+ substrate_client.rb (0.1.8)
5
5
  activesupport (~> 5.2.4)
6
- eventmachine (~> 1.2.7)
7
- faye-websocket (~> 0.10.9)
8
- scale.rb (~> 0.2.2)
9
- substrate_common.rb (~> 0.1.8)
6
+ kontena-websocket-client (~> 0.1.1)
7
+ scale.rb (~> 0.2.11)
8
+ substrate_common.rb (~> 0.1.9)
10
9
 
11
10
  GEM
12
11
  remote: https://rubygems.org/
13
12
  specs:
14
- activesupport (5.2.4.2)
13
+ activesupport (5.2.4.4)
15
14
  concurrent-ruby (~> 1.0, >= 1.0.2)
16
15
  i18n (>= 0.7, < 2)
17
16
  minitest (~> 5.1)
@@ -19,17 +18,15 @@ GEM
19
18
  base58 (0.2.3)
20
19
  blake2b (0.10.0)
21
20
  coderay (1.1.2)
22
- concurrent-ruby (1.1.6)
21
+ concurrent-ruby (1.1.7)
23
22
  diff-lcs (1.3)
24
- eventmachine (1.2.7)
25
- faye-websocket (0.10.9)
26
- eventmachine (>= 0.12.0)
27
- websocket-driver (>= 0.5.1)
28
- i18n (1.8.2)
23
+ i18n (1.8.5)
29
24
  concurrent-ruby (~> 1.0)
30
- json (2.3.0)
25
+ json (2.3.1)
26
+ kontena-websocket-client (0.1.1)
27
+ websocket-driver (~> 0.6.5)
31
28
  method_source (0.9.2)
32
- minitest (5.14.0)
29
+ minitest (5.14.2)
33
30
  pry (0.12.2)
34
31
  coderay (~> 1.1.0)
35
32
  method_source (~> 0.9.0)
@@ -47,12 +44,12 @@ GEM
47
44
  diff-lcs (>= 1.2.0, < 2.0)
48
45
  rspec-support (~> 3.9.0)
49
46
  rspec-support (3.9.2)
50
- scale.rb (0.2.2)
47
+ scale.rb (0.2.11)
51
48
  activesupport (>= 4.0.0)
52
49
  json (~> 2.3.0)
53
- substrate_common.rb (~> 0.1.8)
50
+ substrate_common.rb (~> 0.1.9)
54
51
  thor (~> 0.19.0)
55
- substrate_common.rb (0.1.8)
52
+ substrate_common.rb (0.1.9)
56
53
  base58
57
54
  blake2b
58
55
  xxhash
@@ -60,9 +57,9 @@ GEM
60
57
  thread_safe (0.3.6)
61
58
  tzinfo (1.2.7)
62
59
  thread_safe (~> 0.1)
63
- websocket-driver (0.7.1)
60
+ websocket-driver (0.6.5)
64
61
  websocket-extensions (>= 0.1.0)
65
- websocket-extensions (0.1.4)
62
+ websocket-extensions (0.1.5)
66
63
  xxhash (0.4.0)
67
64
 
68
65
  PLATFORMS
data/README.md CHANGED
@@ -20,23 +20,156 @@ Or install it yourself as:
20
20
 
21
21
  ## Usage
22
22
 
23
- ### Api list
23
+ ### Supported rpc methods
24
24
 
25
25
  ```ruby
26
26
  require "substrate_client"
27
27
 
28
28
  client = SubstrateClient.new("wss://kusama-rpc.polkadot.io/")
29
- puts client.method_list
29
+ client.methods
30
+ ```
31
+ returns like:
32
+ ```shell
33
+ [
34
+ "account_nextIndex",
35
+ "author_hasKey",
36
+ ...
37
+ "chain_getBlock",
38
+ "chain_getBlockHash",
39
+ ...
40
+ ]
41
+ ```
42
+
43
+ The rpc methods can be dynamically called by its name, so you can call it like:
44
+
45
+ ```ruby
46
+ client.chain_getBlockHash(1024)
30
47
  ```
31
- The rpc api methods is dynamically generated, so the methods returned by this method can be called.
32
48
 
33
- ## TODO
49
+ ### Origin rpc methods
50
+
51
+ - `account_nextIndex`
52
+ * `author_hasKey`
53
+ * `author_hasSessionKeys`
54
+ * `author_insertKey`
55
+ * `author_pendingExtrinsics`
56
+ * `author_removeExtrinsic`
57
+ * `author_rotateKeys`
58
+ * `author_submitExtrinsic`
59
+ * `babe_epochAuthorship`
60
+ * `chain_getBlock(block_hash = nil)`
61
+ * `chain_getBlockHash(block_id)`
62
+ * `chain_getFinalisedHead`
63
+ * `chain_getFinalizedHead`
64
+ * `chain_getHead`
65
+ * `chain_getHeader(block_hash = nil)`
66
+ * `chain_getRuntimeVersion(block_hash = nil)`
67
+ * `childstate_getKeys`
68
+ * `childstate_getStorage`
69
+ * `childstate_getStorageHash`
70
+ * `childstate_getStorageSize`
71
+ * `grandpa_roundState`
72
+ * `offchain_localStorageGet`
73
+ * `offchain_localStorageSet`
74
+ * `payment_queryInfo`
75
+ * `state_call`
76
+ * `state_callAt`
77
+ * `state_getKeys`
78
+ * `state_getKeysPaged`
79
+ * `state_getKeysPagedAt`
80
+ * `state_getMetadata(block_hash = nil)`
81
+ * `state_getPairs`
82
+ * `state_getReadProof`
83
+ * `state_getRuntimeVersion`
84
+ * `state_getStorage(storage_key, block_hash = nil)`
85
+ * `state_getStorageAt`
86
+ * `state_getStorageHash`
87
+ * `state_getStorageHashAt`
88
+ * `state_getStorageSize`
89
+ * `state_getStorageSizeAt`
90
+ * `state_queryStorage`
91
+ * `state_queryStorageAt`
92
+ * `system_accountNextIndex`
93
+ * `system_addReservedPeer`
94
+ * `system_chain`
95
+ * `system_chainType`
96
+ * `system_dryRun`
97
+ * `system_dryRunAt`
98
+ * `system_health`
99
+ * `system_localListenAddresses`
100
+ * `system_localPeerId`
101
+ * `system_name`
102
+ * `system_networkState`
103
+ * `system_nodeRoles`
104
+ * `system_peers`
105
+ * `system_properties`
106
+ * `system_removeReservedPeer`
107
+ - `system_version`
108
+
109
+ ### Wrap methods
110
+
111
+ These methods will encode the parameters and decode the returned data
112
+
113
+ - `get_block_number(block_hash)`
114
+
115
+ - `get_metadata(block_hash)`
116
+
117
+ - `get_block(block_hash=nil)`
118
+
119
+ - `get_block_events(block_hash)`
120
+
121
+ - `get_storage(module_name, storage_name, params = nil, block_hash = nil)`
122
+
123
+ ```ruby
124
+ client.get_storage("Balances", "TotalIssuance", nil, nil)
125
+ client.get_storage("System", "Account", ["0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"], nil)
126
+ client.get_storage("ImOnline", "AuthoredBlocks", [2818, "0x749ddc93a65dfec3af27cc7478212cb7d4b0c0357fef35a0163966ab5333b757"], nil)
127
+ ```
128
+
129
+ - `compose_call(module_name, call_name, params, block_hash=nil)`
130
+
131
+ ```ruby
132
+ client.compose_call "Balances", "Transfer", { dest: "0x586cb27c291c813ce74e86a60dad270609abf2fc8bee107e44a80ac00225c409", value: 1_000_000_000_000 }, nil
133
+ ```
134
+
135
+ ## Docker
136
+
137
+ 1. update to latest image
138
+
139
+ `docker pull itering/substrate_client:latest`
140
+
141
+ 2. Run image:
142
+
143
+ `docker run -it itering/substrate_client:latest`
144
+
145
+ This will enter the container with a linux shell opened.
146
+
147
+ ```shell
148
+ /usr/src/app #
149
+ ```
150
+
151
+ 3. Type `rspec` to run all tests
152
+
153
+ ```shell
154
+ /usr/src/app # rspec
155
+ ...................
156
+
157
+ Finished in 0.00883 seconds (files took 0.09656 seconds to load)
158
+ 5 examples, 0 failures
159
+ ```
160
+
161
+ 4. Or, type `./bin/console` to enter the ruby interactive environment and run any decode or encode code
162
+
163
+ ```shell
164
+ /usr/src/app # ./bin/console
165
+ [1] pry(main)> client = SubstrateClient.new("wss://kusama-rpc.polkadot.io/")
166
+ => #<SubstrateClient:0x000055a78f124f58 ...
167
+ [2] pry(main)> client.methods
168
+ => ...
169
+ [3] pry(main)> client.chain_getHead
170
+ => "0xb3c3a220d4639b7c62f179f534b3a66336a115ebc18f13db053f0c57437c45fc"
171
+ ```
34
172
 
35
- - [x] ws wss request support
36
- - [ ] http request support
37
- - [x] generate storage key
38
- - [x] call any api supported by substrate node with ruby's method missing function
39
- - [ ] metadata caching
40
173
 
41
174
  ## Development
42
175
 
@@ -3,10 +3,10 @@
3
3
  require "substrate_client"
4
4
  require "json"
5
5
 
6
- # metadata url block_hash
7
- url = ARGV[0] || "wss://cc3-5.kusama.network/"
8
- block_hash = ARGV[1]
9
-
6
+ url = ARGV[0] || "wss://kusama-rpc.polkadot.io"
10
7
  client = SubstrateClient.new(url)
11
- client.init(block_hash)
12
- puts JSON.pretty_generate(client.metadata.value.value)
8
+
9
+ block_hash = ARGV[1] || client.chain_getFinalisedHead
10
+
11
+ metadata = client.get_metadata(block_hash)
12
+ puts JSON.pretty_generate(metadata.value.to_human)
@@ -0,0 +1,119 @@
1
+
2
+ class SubstrateClient::Helper
3
+ class << self
4
+ def generate_storage_key_from_metadata(metadata, module_name, storage_name, params = nil)
5
+ # find the storage item from metadata
6
+ metadata_modules = metadata.value.value[:metadata][:modules]
7
+ metadata_module = metadata_modules.detect { |mm| mm[:name] == module_name }
8
+ raise "Module '#{module_name}' not exist" unless metadata_module
9
+ storage_item = metadata_module[:storage][:items].detect { |item| item[:name] == storage_name }
10
+ raise "Storage item '#{storage_name}' not exist. \n#{metadata_module.inspect}" unless storage_item
11
+
12
+ if storage_item[:type][:Plain]
13
+ return_type = storage_item[:type][:Plain]
14
+ elsif map = storage_item[:type][:Map]
15
+ raise "Storage call of type \"Map\" requires 1 parameter" if params.nil? || params.length != 1
16
+
17
+ hasher = map[:hasher]
18
+ return_type = map[:value]
19
+ # TODO: decode to account id if param is address
20
+ # params[0] = decode(params[0]) if map[:key] == "AccountId"
21
+ type = Scale::Types.get(map[:key])
22
+ params[0] = type.new(params[0]).encode
23
+ elsif map = storage_item[:type][:DoubleMap]
24
+ raise "Storage call of type \"DoubleMapType\" requires 2 parameters" if params.nil? || params.length != 2
25
+
26
+ hasher = map[:hasher]
27
+ hasher2 = map[:key2Hasher]
28
+ return_type = map[:value]
29
+ params[0] = Scale::Types.get(map[:key1]).new(params[0]).encode
30
+ params[1] = Scale::Types.get(map[:key2]).new(params[1]).encode
31
+ else
32
+ raise NotImplementedError
33
+ end
34
+
35
+ storage_key = generate_storage_key(
36
+ module_name,
37
+ storage_name,
38
+ params,
39
+ hasher,
40
+ hasher2,
41
+ metadata.value.value[:metadata][:version]
42
+ )
43
+ [storage_key, return_type]
44
+ end
45
+
46
+ def generate_storage_key(module_name, storage_name, params = nil, hasher = nil, hasher2 = nil, metadata_version = nil)
47
+ if metadata_version and metadata_version >= 9
48
+ storage_key = Crypto.twox128(module_name) + Crypto.twox128(storage_name)
49
+
50
+ params&.each_with_index do |param, index|
51
+ if index == 0
52
+ param_hasher = hasher
53
+ elsif index == 1
54
+ param_hasher = hasher2
55
+ else
56
+ raise "Unexpected third parameter for storage call"
57
+ end
58
+
59
+ param_key = param.hex_to_bytes
60
+ param_hasher = "Twox128" if param_hasher.nil?
61
+ storage_key += Crypto.send param_hasher.underscore, param_key
62
+ end
63
+
64
+ "0x#{storage_key}"
65
+ else
66
+ # TODO: add test
67
+ storage_key = module_name + " " + storage_name
68
+
69
+ unless params.nil?
70
+ params = [params] if params.class != ::Array
71
+ params_key = params.join("")
72
+ hasher = "Twox128" if hasher.nil?
73
+ storage_key += params_key.hex_to_bytes.bytes_to_utf8
74
+ end
75
+
76
+ "0x#{Crypto.send( hasher.underscore, storage_key )}"
77
+ end
78
+ end
79
+
80
+ def compose_call_from_metadata(metadata, module_name, call_name, params)
81
+ call = metadata.get_module_call(module_name, call_name)
82
+
83
+ value = {
84
+ call_index: call[:lookup],
85
+ module_name: module_name,
86
+ call_name: call_name,
87
+ params: []
88
+ }
89
+
90
+ params.keys.each_with_index do |call_param_name, i|
91
+ param_value = params[call_param_name]
92
+ value[:params] << {
93
+ name: call_param_name.to_s,
94
+ type: call[:args][i][:type],
95
+ value: param_value
96
+ }
97
+ end
98
+
99
+ Scale::Types::Extrinsic.new(value).encode
100
+ end
101
+
102
+ def decode_block(block)
103
+ block["block"]["header"]["number"] = block["block"]["header"]["number"].to_i(16)
104
+
105
+ block["block"]["extrinsics"].each_with_index do |hex, i|
106
+ scale_bytes = Scale::Bytes.new(hex)
107
+ block["block"]["extrinsics"][i] = Scale::Types::Extrinsic.decode(scale_bytes).to_human
108
+ end
109
+
110
+ block['block']['header']["digest"]["logs"].each_with_index do |hex, i|
111
+ scale_bytes = Scale::Bytes.new(hex)
112
+ block['block']['header']["digest"]["logs"][i] = Scale::Types::LogDigest.decode(scale_bytes).to_human
113
+ end
114
+
115
+ block
116
+ end
117
+
118
+ end
119
+ end
@@ -1,52 +1,45 @@
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 "helper"
9
+ require 'kontena-websocket-client'
11
10
 
12
11
  def ws_request(url, payload)
13
12
  result = nil
13
+ Kontena::Websocket::Client.connect(url, {}) do |client|
14
+ client.send(payload.to_json)
14
15
 
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
16
+ client.read do |message|
17
+ result = JSON.parse message
18
+ client.close(1000)
35
19
  end
36
20
  end
37
21
 
38
- result
22
+ return result
23
+ rescue Kontena::Websocket::CloseError => e
24
+ raise SubstrateClient::WebsocketError, e.reason
25
+ rescue Kontena::Websocket::Error => e
26
+ raise SubstrateClient::WebsocketError, e.reason
39
27
  end
40
28
 
41
29
  class SubstrateClient
30
+ class WebsocketError < StandardError; end
31
+ class RpcError < StandardError; end
32
+ class RpcTimeout < StandardError; end
33
+
42
34
  attr_accessor :spec_name, :spec_version, :metadata
43
35
 
44
- def initialize(url)
36
+ def initialize(url, spec_name: nil)
45
37
  @url = url
46
38
  @request_id = 1
39
+ @spec_name = spec_name
40
+ Scale::TypeRegistry.instance.load(spec_name: spec_name)
47
41
  end
48
42
 
49
- # TODO: error
50
43
  def request(method, params)
51
44
  payload = {
52
45
  "jsonrpc" => "2.0",
@@ -54,145 +47,99 @@ class SubstrateClient
54
47
  "params" => params,
55
48
  "id" => @request_id
56
49
  }
57
- @request_id += 1
58
- ws_request(@url, payload)
50
+
51
+ data = ws_request(@url, payload)
52
+ if data["error"]
53
+ raise RpcError, data["error"]
54
+ else
55
+ data["result"]
56
+ end
57
+ end
58
+
59
+ def init_runtime(block_hash=nil)
60
+ # set current runtime spec version
61
+ runtime_version = self.state_getRuntimeVersion(block_hash)
62
+ @spec_version = runtime_version["specVersion"]
63
+ Scale::TypeRegistry.instance.spec_version = @spec_version
64
+
65
+ # set current metadata
66
+ @metadata = self.get_metadata(block_hash)
67
+ Scale::TypeRegistry.instance.metadata = @metadata.value
68
+ true
69
+ end
70
+
71
+ def invoke(method, *params)
72
+ # params.reject! { |param| param.nil? }
73
+ request(method, params)
59
74
  end
60
75
 
61
- # ############################
62
- # native rpc methods support
63
- # ############################
76
+ # ################################################
77
+ # origin rpc methods
78
+ # ################################################
64
79
  def method_missing(method, *args)
65
- data = request(SubstrateClient.real_method_name(method), args)
66
- data["result"]
80
+ invoke method, *args
67
81
  end
68
82
 
69
83
  # ################################################
70
- # custom methods wrapped from native rpc methods
84
+ # custom methods based on origin rpc methods
71
85
  # ################################################
72
- def method_list
73
- methods = self.rpc_methods["methods"].map(&:underscore)
74
- methods << "method_list"
75
- methods << "get_storage_at"
86
+ def methods
87
+ invoke("rpc_methods")["methods"]
76
88
  end
77
89
 
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"]
90
+ def get_block_number(block_hash)
91
+ header = self.chain_getHeader(block_hash)
92
+ header["number"].to_i(16)
93
+ end
82
94
 
83
- Scale::TypeRegistry.instance.load(spec_name, spec_version)
84
- @metadata = self.get_metadata(block_hash)
85
- true
95
+ def get_metadata(block_hash=nil)
96
+ hex = self.state_getMetadata(block_hash)
97
+ Scale::Types::Metadata.decode(Scale::Bytes.new(hex))
86
98
  end
87
99
 
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]
100
+ def get_block(block_hash=nil)
101
+ self.init_runtime(block_hash)
102
+ block = self.chain_getBlock(block_hash)
103
+ SubstrateClient::Helper.decode_block(block)
92
104
  end
93
105
 
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
128
- end
106
+ def get_block_events(block_hash=nil)
107
+ self.init_runtime(block_hash)
108
+
109
+ storage_key = "0x26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7"
110
+ events_data = state_getStorage storage_key, block_hash
129
111
 
130
- storage_hash = SubstrateClient.generate_storage_hash(
131
- module_name,
132
- storage_function_name,
133
- params,
134
- hasher,
135
- hasher2,
136
- metadata[:version]
137
- )
112
+ scale_bytes = Scale::Bytes.new(events_data)
113
+ Scale::Types.get("Vec<EventRecord>").decode(scale_bytes).to_human
114
+ end
115
+
116
+ # Plain: client.get_storage("Sudo", "Key")
117
+ # Plain: client.get_storage("Balances", "TotalIssuance")
118
+ # Map: client.get_storage("System", "Account", ["0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"])
119
+ # DoubleMap: client.get_storage("ImOnline", "AuthoredBlocks", [2818, "0x749ddc93a65dfec3af27cc7478212cb7d4b0c0357fef35a0163966ab5333b757"])
120
+ def get_storage(module_name, storage_name, params = nil, block_hash = nil)
121
+ self.init_runtime(block_hash)
138
122
 
139
- # puts storage_hash
123
+ storage_key, return_type = SubstrateClient::Helper.generate_storage_key_from_metadata(@metadata, module_name, storage_name, params)
140
124
 
141
- result = self.state_get_storage_at(storage_hash, block_hash)
125
+ result = self.state_getStorage(storage_key, block_hash)
142
126
  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
127
+ Scale::Types.get(return_type).decode(Scale::Bytes.new(result))
147
128
  end
148
129
 
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
163
-
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
167
- end
168
- end
169
-
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
181
-
182
- "0x#{Crypto.send( hasher.underscore, storage_hash )}"
183
- end
184
- end
185
-
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
192
- end
130
+ def generate_storage_key(module_name, storage_name, params = nil, block_hash = nil)
131
+ self.init_runtime(block_hash)
132
+ SubstrateClient::Helper.generate_storage_key_from_metadata(@metadata, module_name, storage_name, params)
133
+ end
193
134
 
135
+ # compose_call "Balances", "Transfer", { dest: "0x586cb27c291c813ce74e86a60dad270609abf2fc8bee107e44a80ac00225c409", value: 1_000_000_000_000 }
136
+ def compose_call(module_name, call_name, params, block_hash=nil)
137
+ self.init_runtime(block_hash)
138
+ SubstrateClient::Helper.compose_call_from_metadata(@metadata, module_name, call_name, params)
194
139
  end
195
140
 
141
+ def generate_storage_hash_from_data(storage_hex_data)
142
+ "0x" + Crypto.blake2_256(Scale::Bytes.new(storage_hex_data).bytes)
143
+ end
196
144
 
197
145
  end
198
-
@@ -1,3 +1,3 @@
1
1
  class SubstrateClient
2
- VERSION = "0.1.3"
2
+ VERSION = "0.1.8"
3
3
  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
- spec.add_dependency "faye-websocket", "~> 0.10.9"
41
- spec.add_dependency "eventmachine", "~> 1.2.7"
42
39
  spec.add_dependency "activesupport", "~> 5.2.4"
43
- spec.add_dependency "scale.rb", "~> 0.2.2"
40
+ spec.add_dependency "scale.rb", "~> 0.2.11"
41
+ spec.add_dependency "kontena-websocket-client", "~> 0.1.1"
42
+ spec.add_dependency "substrate_common.rb", "~> 0.1.9"
44
43
 
45
44
  spec.add_development_dependency "bundler", "~> 1.17"
46
45
  spec.add_development_dependency "rake", ">= 12.3.3"
metadata CHANGED
@@ -1,85 +1,71 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: substrate_client.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.8
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-11-01 00:00:00.000000000 Z
12
12
  dependencies:
13
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
- - !ruby/object:Gem::Dependency
28
- name: faye-websocket
14
+ name: activesupport
29
15
  requirement: !ruby/object:Gem::Requirement
30
16
  requirements:
31
17
  - - "~>"
32
18
  - !ruby/object:Gem::Version
33
- version: 0.10.9
19
+ version: 5.2.4
34
20
  type: :runtime
35
21
  prerelease: false
36
22
  version_requirements: !ruby/object:Gem::Requirement
37
23
  requirements:
38
24
  - - "~>"
39
25
  - !ruby/object:Gem::Version
40
- version: 0.10.9
26
+ version: 5.2.4
41
27
  - !ruby/object:Gem::Dependency
42
- name: eventmachine
28
+ name: scale.rb
43
29
  requirement: !ruby/object:Gem::Requirement
44
30
  requirements:
45
31
  - - "~>"
46
32
  - !ruby/object:Gem::Version
47
- version: 1.2.7
33
+ version: 0.2.11
48
34
  type: :runtime
49
35
  prerelease: false
50
36
  version_requirements: !ruby/object:Gem::Requirement
51
37
  requirements:
52
38
  - - "~>"
53
39
  - !ruby/object:Gem::Version
54
- version: 1.2.7
40
+ version: 0.2.11
55
41
  - !ruby/object:Gem::Dependency
56
- name: activesupport
42
+ name: kontena-websocket-client
57
43
  requirement: !ruby/object:Gem::Requirement
58
44
  requirements:
59
45
  - - "~>"
60
46
  - !ruby/object:Gem::Version
61
- version: 5.2.4
47
+ version: 0.1.1
62
48
  type: :runtime
63
49
  prerelease: false
64
50
  version_requirements: !ruby/object:Gem::Requirement
65
51
  requirements:
66
52
  - - "~>"
67
53
  - !ruby/object:Gem::Version
68
- version: 5.2.4
54
+ version: 0.1.1
69
55
  - !ruby/object:Gem::Dependency
70
- name: scale.rb
56
+ name: substrate_common.rb
71
57
  requirement: !ruby/object:Gem::Requirement
72
58
  requirements:
73
59
  - - "~>"
74
60
  - !ruby/object:Gem::Version
75
- version: 0.2.2
61
+ version: 0.1.9
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.2
68
+ version: 0.1.9
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,7 +143,7 @@ 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
163
149
  - substrate_client.gemspec
@@ -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)