scale.rb 0.2.16 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/Cargo.lock +8 -4
  4. data/Cargo.toml +2 -3
  5. data/Dockerfile +4 -1
  6. data/Gemfile.lock +43 -35
  7. data/README.md +44 -1
  8. data/Rakefile +6 -0
  9. data/exe/scale +39 -79
  10. data/lib/address.rb +3 -0
  11. data/lib/common.rb +163 -0
  12. data/lib/helper.rb +25 -8
  13. data/lib/metadata/metadata.rb +28 -18
  14. data/lib/metadata/metadata_v0.rb +24 -20
  15. data/lib/metadata/metadata_v1.rb +13 -9
  16. data/lib/metadata/metadata_v10.rb +2 -2
  17. data/lib/metadata/metadata_v11.rb +2 -2
  18. data/lib/metadata/metadata_v12.rb +9 -8
  19. data/lib/metadata/metadata_v13.rb +161 -0
  20. data/lib/metadata/metadata_v2.rb +2 -2
  21. data/lib/metadata/metadata_v3.rb +2 -2
  22. data/lib/metadata/metadata_v4.rb +21 -11
  23. data/lib/metadata/metadata_v5.rb +21 -11
  24. data/lib/metadata/metadata_v6.rb +9 -9
  25. data/lib/metadata/metadata_v7.rb +26 -15
  26. data/lib/metadata/metadata_v8.rb +9 -9
  27. data/lib/metadata/metadata_v9.rb +2 -2
  28. data/lib/scale.rb +41 -341
  29. data/lib/scale/base.rb +177 -95
  30. data/lib/scale/block.rb +17 -13
  31. data/lib/scale/trie.rb +1 -1
  32. data/lib/scale/types.rb +139 -40
  33. data/lib/scale/version.rb +1 -1
  34. data/lib/scale_bytes.rb +63 -0
  35. data/lib/substrate_client.rb +31 -17
  36. data/lib/type_builder.rb +279 -0
  37. data/lib/type_registry.rb +91 -0
  38. data/lib/type_registry/crab.json +676 -595
  39. data/lib/type_registry/darwinia.json +730 -554
  40. data/lib/type_registry/default.json +3 -2
  41. data/lib/type_registry/pangolin.json +771 -0
  42. data/scale.gemspec +7 -5
  43. data/scripts/mmr_root_to_sign.rb +10 -0
  44. data/src/lib.rs +80 -25
  45. metadata +59 -30
  46. data/lib/type_registry/edgeware.json +0 -124
  47. data/lib/type_registry/joystream.json +0 -49
  48. data/lib/type_registry/kulupu.json +0 -15
  49. data/lib/type_registry/plasm.json +0 -89
  50. data/lib/type_registry/robonomics.json +0 -39
  51. data/lib/type_registry/westend.json +0 -63
  52. data/src/storage_key.rs +0 -41
data/lib/scale/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Scale
2
- VERSION = "0.2.16".freeze
2
+ VERSION = "0.3.0".freeze
3
3
  end
@@ -0,0 +1,63 @@
1
+ module Scale
2
+ class Bytes
3
+ attr_reader :data, :bytes
4
+ attr_reader :offset
5
+
6
+ def initialize(data)
7
+ if (data.class == Array) && data.is_byte_array?
8
+ @bytes = data
9
+ elsif (data.class == String) && data.start_with?("0x") && (data.length % 2 == 0)
10
+ arr = data[2..].scan(/../).map(&:hex)
11
+ @bytes = arr
12
+ else
13
+ raise "Provided data is not valid"
14
+ end
15
+
16
+ @data = data
17
+ @offset = 0
18
+ end
19
+
20
+ def reset_offset
21
+ @offset = 0
22
+ end
23
+
24
+ def get_next_bytes(length)
25
+ result = @bytes[@offset...@offset + length]
26
+ if result.length < length
27
+ str = @data[(2 + @offset * 2)..]
28
+ str = str.length > 40 ? (str[0...40]).to_s + "..." : str
29
+ raise "No enough data: #{str}, expect length: #{length}, but #{result.length}"
30
+ end
31
+ @offset += length
32
+ result
33
+ rescue RangeError => ex
34
+ puts "length: #{length}"
35
+ puts ex.message
36
+ puts ex.backtrace
37
+ end
38
+
39
+ def get_remaining_bytes
40
+ @bytes[offset..]
41
+ end
42
+
43
+ def to_hex_string
44
+ @bytes.bytes_to_hex
45
+ end
46
+
47
+ def to_bin_string
48
+ @bytes.bytes_to_bin
49
+ end
50
+
51
+ def to_ascii
52
+ @bytes[0...offset].pack("C*") + "<================================>" + @bytes[offset..].pack("C*")
53
+ end
54
+
55
+ def ==(other)
56
+ bytes == other.bytes && offset == other.offset
57
+ end
58
+
59
+ def to_s
60
+ green(@bytes[0...offset].bytes_to_hex) + yellow(@bytes[offset..].bytes_to_hex[2..])
61
+ end
62
+ end
63
+ end
@@ -1,20 +1,30 @@
1
+ require "faye/websocket"
2
+ require "eventmachine"
1
3
 
2
4
  def ws_request(url, payload)
3
5
  result = nil
4
- Kontena::Websocket::Client.connect(url, {}) do |client|
5
- client.send(payload.to_json)
6
6
 
7
- client.read do |message|
8
- result = JSON.parse message
9
- client.close(1000)
7
+ EM.run do
8
+ ws = Faye::WebSocket::Client.new(url)
9
+
10
+ ws.on :open do |event|
11
+ ws.send(payload.to_json)
12
+ end
13
+
14
+ ws.on :message do |event|
15
+ if event.data.include?("jsonrpc")
16
+ result = JSON.parse event.data
17
+ ws.close(3001, "data received")
18
+ EM.stop
19
+ end
20
+ end
21
+
22
+ ws.on :close do |event|
23
+ ws = nil
10
24
  end
11
25
  end
12
26
 
13
- return result
14
- rescue Kontena::Websocket::CloseError => e
15
- raise SubstrateClient::WebsocketError, e.reason
16
- rescue Kontena::Websocket::Error => e
17
- raise SubstrateClient::WebsocketError, e.reason
27
+ result
18
28
  end
19
29
 
20
30
  class SubstrateClient
@@ -29,6 +39,7 @@ class SubstrateClient
29
39
  @url = url
30
40
  @request_id = 1
31
41
  @metadata_cache = {}
42
+ init_types_and_metadata
32
43
  end
33
44
 
34
45
  def request(method, params)
@@ -122,19 +133,22 @@ class SubstrateClient
122
133
  events_data = state_getStorage storage_key, block_hash
123
134
 
124
135
  scale_bytes = Scale::Bytes.new(events_data)
125
- Scale::Types.get("Vec<EventRecord>").decode(scale_bytes).to_human
136
+ decoded = Scale::Types.get("Vec<EventRecord>").decode(scale_bytes).to_human
137
+ [events_data, decoded]
126
138
  end
127
139
 
128
- # Plain: client.get_storage("Sudo", "Key")
129
- # Plain: client.get_storage("Balances", "TotalIssuance")
130
- # Map: client.get_storage("System", "Account", ["0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"])
131
- # DoubleMap: client.get_storage("ImOnline", "AuthoredBlocks", [2818, "0x749ddc93a65dfec3af27cc7478212cb7d4b0c0357fef35a0163966ab5333b757"])
132
140
  def get_storage(module_name, storage_name, params = nil, block_hash = nil)
133
141
  self.init_types_and_metadata(block_hash)
134
142
 
135
- storage_key, return_type = SubstrateClient::Helper.generate_storage_key_from_metadata(@metadata, module_name, storage_name, params)
143
+ storage_key, return_type, storage_item = SubstrateClient::Helper.generate_storage_key_from_metadata(@metadata, module_name, storage_name, params)
144
+
136
145
  data = self.state_getStorage(storage_key, block_hash)
137
- return unless data
146
+
147
+ if data.nil?
148
+ return if storage_item[:modifier] == "Optional"
149
+
150
+ data = storage_item[:fallback]
151
+ end
138
152
 
139
153
  bytes = Scale::Bytes.new(data)
140
154
  type = Scale::Types.get(return_type)
@@ -0,0 +1,279 @@
1
+ module Scale
2
+ module Types
3
+ class << self
4
+
5
+ # type_info: type_string or type_def
6
+ # type_string: hard coded type name, Compact, H128, Vec<Compact>, (U32, U128), ...
7
+ # type_def : struct, enum, set
8
+ #
9
+ # if type_string start_with Scale::Types::, it is treat as a hard coded type
10
+ def get(type_info)
11
+ if type_info.class == ::String
12
+ if type_info.start_with?('Scale::Types::')
13
+ return get_hard_coded_type(type_info)
14
+ end
15
+
16
+ # find the final type from registry
17
+ type_info = fix_name(type_info)
18
+ type_info = get_final_type_from_registry(type_info)
19
+ end
20
+
21
+ build_type(type_info)
22
+ end
23
+
24
+ private
25
+ def build_type(type_info)
26
+ # 1. hard coded types, 2. Vec<...>, 3. Option<...>, 4. [xx; x], 5. (x, y)
27
+ if type_info.class == ::String
28
+ type_string = fix_name(type_info)
29
+ if type_string =~ /\AVec<.+>\z/
30
+ build_vec(type_string)
31
+ elsif type_info =~ /\AOption<.+>\z/
32
+ build_option(type_string)
33
+ elsif type_info =~ /\A\[.+;\s*\d+\]\z/
34
+ build_array(type_string)
35
+ elsif type_info =~ /\A\(.+\)\z/
36
+ build_tuple(type_string)
37
+ else
38
+ get_hard_coded_type(type_string)
39
+ end
40
+
41
+ # 5. Struct, 6. Enum, 7. Set
42
+ else
43
+
44
+ if type_info["type"] == "struct"
45
+ build_struct(type_info)
46
+ elsif type_info["type"] == "enum"
47
+ build_enum(type_info)
48
+ elsif type_info["type"] == "set"
49
+ build_set(type_info)
50
+ else
51
+ raise Scale::TypeBuildError.new("Failed to build a type from #{type_info}")
52
+ end
53
+
54
+ end
55
+ end
56
+
57
+ def get_final_type_from_registry(type_info)
58
+ type_registry = TypeRegistry.instance
59
+ if type_registry.types.nil?
60
+ raise TypeRegistryNotLoadYet
61
+ end
62
+ TypeRegistry.instance.get(type_info)
63
+ end
64
+
65
+ def get_hard_coded_type(type_name)
66
+ # type_name = rename(type_name)
67
+ type_name = (type_name.start_with?("Scale::Types::") ? type_name : "Scale::Types::#{type_name}")
68
+ type_name.constantize2
69
+ rescue => e
70
+ raise Scale::TypeBuildError.new("Failed to get the hard coded type named `#{type_name}`")
71
+ end
72
+
73
+ def build_vec(type_string)
74
+ inner_type_str = type_string.scan(/\AVec<(.+)>\z/).first.first
75
+ inner_type = get(inner_type_str)
76
+
77
+ type_name = "Vec_#{inner_type.name.gsub('Scale::Types::', '')}_"
78
+
79
+ if !Scale::Types.const_defined?(type_name)
80
+ klass = Class.new do
81
+ include Scale::Types::Vec
82
+ inner_type inner_type
83
+ end
84
+ Scale::Types.const_set type_name, klass
85
+ else
86
+ Scale::Types.const_get type_name
87
+ end
88
+ end
89
+
90
+ def build_option(type_string)
91
+ inner_type_str = type_string.scan(/\AOption<(.+)>\z/).first.first
92
+
93
+ # an exception
94
+ # https://substrate.dev/docs/en/knowledgebase/advanced/codec#options
95
+ return get("Scale::Types::OptionBool") if inner_type_str.camelize2 == "Bool"
96
+
97
+ inner_type = get(inner_type_str)
98
+
99
+ type_name = "Option_#{inner_type.name.gsub('Scale::Types::', '')}_"
100
+
101
+ if !Scale::Types.const_defined?(type_name)
102
+ klass = Class.new do
103
+ include Scale::Types::Option
104
+ inner_type inner_type
105
+ end
106
+ Scale::Types.const_set type_name, klass
107
+ else
108
+ Scale::Types.const_get type_name
109
+ end
110
+ end
111
+
112
+ def build_array(type_string)
113
+ scan_result = type_string.scan /\[(.+);\s*(\d+)\]/
114
+
115
+ #
116
+ inner_type_str = scan_result[0][0]
117
+ inner_type = get(inner_type_str)
118
+
119
+ #
120
+ len = scan_result[0][1].to_i
121
+
122
+ type_name = "Array_#{inner_type.name.gsub('Scale::Types::', '')}_#{len}_"
123
+
124
+ if !Scale::Types.const_defined?(type_name)
125
+ klass = Class.new do
126
+ include Scale::Types::Array
127
+ inner_type inner_type
128
+ length len
129
+ end
130
+ Scale::Types.const_set type_name, klass
131
+ else
132
+ Scale::Types.const_get type_name
133
+ end
134
+ end
135
+
136
+ def build_tuple(type_string)
137
+ scan_result = type_string.scan /\A\((.+)\)\z/
138
+ inner_types_str = scan_result[0][0]
139
+ inner_type_strs = inner_types_str.split(",").map do |inner_type_str|
140
+ inner_type_str.strip
141
+ end
142
+
143
+ inner_types = inner_type_strs.map do |inner_type_str|
144
+ get(inner_type_str)
145
+ end
146
+
147
+ type_name = "Tuple_#{inner_types.map {|inner_type| inner_type.name.gsub('Scale::Types::', '')}.join("_")}_"
148
+ if !Scale::Types.const_defined?(type_name)
149
+ klass = Class.new do
150
+ include Scale::Types::Tuple
151
+ inner_types(*inner_types)
152
+ end
153
+ Scale::Types.const_set type_name, klass
154
+ else
155
+ Scale::Types.const_get type_name
156
+ end
157
+ end
158
+
159
+ def build_struct(type_info)
160
+ # items: {"a" => Type}
161
+ items = type_info["type_mapping"].map do |item|
162
+ item_name = item[0]
163
+ item_type = get(item[1])
164
+ [item_name, item_type]
165
+ end.to_h
166
+
167
+ partials = []
168
+ items.each_pair do |item_name, item_type|
169
+ partials << item_name.camelize2 + 'In' + item_type.name.gsub('Scale::Types::', '')
170
+ end
171
+ type_name = "Struct_#{partials.join('_')}_"
172
+
173
+ if !Scale::Types.const_defined?(type_name)
174
+ klass = Class.new do
175
+ include Scale::Types::Struct
176
+ items(**items)
177
+ end
178
+ Scale::Types.const_set type_name, klass
179
+ else
180
+ Scale::Types.const_get type_name
181
+ end
182
+ end
183
+
184
+ # not implemented: ["Compact", "Hex"]
185
+ def build_enum(type_info)
186
+ # type_info: [["Item1", "Compact"], [["Item2", "Hex"]]
187
+ if type_info.has_key?("type_mapping")
188
+ # items: {a: Type}
189
+ items = type_info["type_mapping"].map do |item|
190
+ item_name = item[0]
191
+ item_type = get(item[1])
192
+ [item_name.to_sym, item_type]
193
+ end.to_h
194
+
195
+ partials = []
196
+ items.each_pair do |item_name, item_type|
197
+ partials << item_name.to_s.camelize2 + 'In' + item_type.name.gsub('Scale::Types::', '')
198
+ end
199
+ type_name = "Enum_#{partials.join('_')}_"
200
+
201
+ if !Scale::Types.const_defined?(type_name)
202
+ klass = Class.new do
203
+ include Scale::Types::Enum
204
+ items(**items)
205
+ end
206
+
207
+ return Scale::Types.const_set type_name, klass
208
+ else
209
+ return Scale::Types.const_get type_name
210
+ end
211
+ end
212
+
213
+ # [1, "hello"]
214
+ if type_info.has_key?("value_list")
215
+ type_name = "Enum#{type_info["value_list"].map {|value| value.to_s.camelize2}.join}"
216
+
217
+ if !Scale::Types.const_defined?(type_name)
218
+ klass = Class.new do
219
+ include Scale::Types::Enum
220
+ values *type_info["value_list"]
221
+ end
222
+ return Scale::Types.const_set type_name, klass
223
+ else
224
+ return Scale::Types.const_get type_name
225
+ end
226
+ end
227
+ end
228
+
229
+
230
+ # {
231
+ # value_type: u32,
232
+ # value_list: {
233
+ # "TransactionPayment" => 0b00000001,
234
+ # "Transfer" => 0b00000010,
235
+ # "Reserve" => 0b00000100,
236
+ # ...
237
+ # }
238
+ # }
239
+ def build_set(type_info)
240
+ type_name = "Set#{type_info["value_list"].keys.map(&:camelize2).join("")}"
241
+ if !Scale::Types.const_defined?(type_name)
242
+ bytes_length = type_info["value_type"][1..].to_i / 8
243
+ klass = Class.new do
244
+ include Scale::Types::Set
245
+ items type_info["value_list"], bytes_length
246
+ end
247
+ return Scale::Types.const_set type_name, klass
248
+ else
249
+ return Scale::Types.const_get type_name
250
+ end
251
+ Scale::Types.const_set fix(name), klass
252
+ end
253
+
254
+ def fix_name(type)
255
+ type = type.gsub("T::", "")
256
+ .gsub("<T>", "")
257
+ .gsub("<T as Trait>::", "")
258
+ .delete("\n")
259
+ .gsub(/(u)(\d+)/, 'U\2')
260
+ return "Bool" if type == "bool"
261
+ return "Null" if type == "()"
262
+ return "String" if type == "Vec<u8>"
263
+ return "Compact" if type == "Compact<u32>" || type == "Compact<U32>"
264
+ return "Address" if type == "<Lookup as StaticLookup>::Source"
265
+ return "Compact" if type == "<Balance as HasCompact>::Type"
266
+ return "Compact" if type == "<BlockNumber as HasCompact>::Type"
267
+ return "Compact" if type =~ /\ACompact<[a-zA-Z0-9\s]*>\z/
268
+ return "CompactMoment" if type == "<Moment as HasCompact>::Type"
269
+ return "CompactMoment" if type == "Compact<Moment>"
270
+ return "InherentOfflineReport" if type == "<InherentOfflineReport as InherentOfflineReport>::Inherent"
271
+ return "AccountData" if type == "AccountData<Balance>"
272
+ return "EventRecord" if type == "EventRecord<Event, Hash>"
273
+
274
+ type
275
+ end
276
+
277
+ end
278
+ end
279
+ end
@@ -0,0 +1,91 @@
1
+ module Scale
2
+
3
+ class TypeRegistry
4
+ include Singleton
5
+
6
+ # init by load, and will not change
7
+ attr_reader :spec_name, :types
8
+ attr_reader :versioning, :custom_types # optional
9
+
10
+ # will change by different spec version
11
+ attr_accessor :spec_version # optional
12
+ attr_accessor :metadata
13
+
14
+ def load(spec_name: nil, custom_types: nil)
15
+ @spec_name = nil
16
+ @types = nil
17
+ @versioning = nil
18
+ @custom_types = nil
19
+
20
+ default_types, _, _ = load_chain_spec_types("default")
21
+
22
+ if spec_name
23
+ begin
24
+ @spec_name = spec_name
25
+ spec_types, @versioning, @spec_version = load_chain_spec_types(spec_name)
26
+ @types = default_types.merge(spec_types)
27
+ rescue => ex
28
+ # TODO: check different errors
29
+ puts "There is no types json file named #{spec_name}"
30
+ @types = default_types
31
+ end
32
+ else
33
+ @spec_name = "default"
34
+ @types = default_types
35
+ end
36
+
37
+ self.custom_types = custom_types
38
+ true
39
+ end
40
+
41
+ def get(type_name)
42
+ all_types = self.all_types
43
+ type_traverse(type_name, all_types)
44
+ end
45
+
46
+ def custom_types=(custom_types)
47
+ @custom_types = custom_types.stringify_keys if (not custom_types.nil?) && custom_types.class.name == "Hash"
48
+ end
49
+
50
+ def all_types
51
+ all_types = {}.merge(@types)
52
+
53
+ if @spec_version && @versioning
54
+ @versioning.each do |item|
55
+ if @spec_version >= item["runtime_range"][0] &&
56
+ ( item["runtime_range"][1].nil? || @spec_version <= item["runtime_range"][1] )
57
+ all_types.merge!(item["types"])
58
+ end
59
+ end
60
+ end
61
+
62
+ all_types.merge!(@custom_types) if @custom_types
63
+ all_types
64
+ end
65
+
66
+ private
67
+
68
+ def load_chain_spec_types(spec_name)
69
+ file = File.join File.expand_path("../..", __FILE__), "lib", "type_registry", "#{spec_name}.json"
70
+ json_string = File.open(file).read
71
+ json = JSON.parse(json_string)
72
+
73
+ runtime_id = json["runtime_id"]
74
+
75
+ [json["types"], json["versioning"], runtime_id]
76
+ end
77
+
78
+ def type_traverse(type, types)
79
+ if type.class == ::String
80
+ real_type = types[type]
81
+
82
+ return type if real_type.nil? || real_type == type
83
+
84
+ type_traverse(real_type, types)
85
+ else
86
+ type
87
+ end
88
+ end
89
+ end
90
+
91
+ end