substrate_client.rb 0.1.3 → 0.1.8

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