scale.rb 0.2.16 → 0.3.0

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