scale.rb 0.2.9 → 0.2.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Cargo.lock +2007 -11
- data/Cargo.toml +9 -2
- data/Gemfile.lock +15 -9
- data/exe/scale +9 -0
- data/lib/helper.rb +126 -0
- data/lib/metadata/metadata.rb +14 -0
- data/lib/metadata/metadata_v12.rb +3 -5
- data/lib/scale.rb +28 -14
- data/lib/scale/base.rb +5 -8
- data/lib/scale/block.rb +11 -25
- data/lib/scale/trie.rb +171 -0
- data/lib/scale/types.rb +61 -31
- data/lib/scale/version.rb +1 -1
- data/lib/substrate_client.rb +159 -0
- data/lib/type_registry/crab.json +929 -0
- data/lib/type_registry/darwinia.json +701 -136
- data/lib/type_registry/default.json +0 -1
- data/scale.gemspec +3 -2
- data/scripts/block_events.rb +2 -3
- data/src/lib.rs +42 -1
- data/src/storage_key.rs +41 -0
- metadata +31 -13
- data/.DS_Store +0 -0
data/lib/scale/trie.rb
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
module Scale
|
2
|
+
module Types
|
3
|
+
|
4
|
+
class TrieNode
|
5
|
+
include SingleValue
|
6
|
+
EMPTY = 0
|
7
|
+
NIBBLE_PER_BYTE = 2
|
8
|
+
BITMAP_LENGTH = 2
|
9
|
+
NIBBLE_LENGTH = 16
|
10
|
+
NIBBLE_SIZE_BOUND = 65535
|
11
|
+
|
12
|
+
def self.decode(scale_bytes)
|
13
|
+
hash = "0x#{Crypto::blake2_256(scale_bytes.bytes)}"
|
14
|
+
first = scale_bytes.get_next_bytes(1).first
|
15
|
+
if first == EMPTY
|
16
|
+
TrieNode.new({})
|
17
|
+
else
|
18
|
+
v = first & (0b11 << 6)
|
19
|
+
decode_size = -> {
|
20
|
+
result = first & 255 >> 2
|
21
|
+
return result if result < 63
|
22
|
+
result -= 1
|
23
|
+
while result <= NIBBLE_SIZE_BOUND
|
24
|
+
n = scale_bytes.get_next_bytes(1).first
|
25
|
+
return (result + n + 1) if n < 255
|
26
|
+
result += 255
|
27
|
+
end
|
28
|
+
return NIBBLE_SIZE_BOUND
|
29
|
+
}
|
30
|
+
|
31
|
+
if v == 0b01 << 6 # leaf
|
32
|
+
nibble_count = decode_size.call
|
33
|
+
# if nibble_count is odd, the half of first byte of partial is 0
|
34
|
+
padding = (nibble_count % NIBBLE_PER_BYTE) != 0
|
35
|
+
first_byte_of_partial = scale_bytes.bytes[scale_bytes.offset]
|
36
|
+
if padding && (first_byte_of_partial & 0xf0) != 0
|
37
|
+
raise "bad format"
|
38
|
+
end
|
39
|
+
|
40
|
+
### partial decoding
|
41
|
+
partial_bytes = scale_bytes.get_next_bytes((nibble_count + (NIBBLE_PER_BYTE - 1)) / NIBBLE_PER_BYTE)
|
42
|
+
|
43
|
+
### value
|
44
|
+
count = Compact.decode(scale_bytes).value
|
45
|
+
value_bytes = scale_bytes.get_next_bytes(count)
|
46
|
+
|
47
|
+
return TrieNode.new({
|
48
|
+
hash: hash,
|
49
|
+
node_type: "leaf",
|
50
|
+
partial: {
|
51
|
+
hex: partial_bytes.bytes_to_hex,
|
52
|
+
padding: padding
|
53
|
+
},
|
54
|
+
value: value_bytes.bytes_to_hex
|
55
|
+
})
|
56
|
+
elsif v == 0b10 << 6 || v == 0b11 << 6 # branch without mask || branch with mask
|
57
|
+
nibble_count = decode_size.call
|
58
|
+
|
59
|
+
### check that the padding is valid (if any)
|
60
|
+
# if nibble_count is odd, the half of first byte of partial is 0
|
61
|
+
padding = nibble_count % NIBBLE_PER_BYTE != 0
|
62
|
+
first_byte_of_partial = scale_bytes.bytes[scale_bytes.offset]
|
63
|
+
if padding && (first_byte_of_partial & 0xf0) != 0
|
64
|
+
raise "bad format"
|
65
|
+
end
|
66
|
+
|
67
|
+
### partial decoding
|
68
|
+
partial_bytes = scale_bytes.get_next_bytes((nibble_count + (NIBBLE_PER_BYTE - 1)) / NIBBLE_PER_BYTE)
|
69
|
+
|
70
|
+
### value decoding
|
71
|
+
if v == 0b11 << 6 # has value
|
72
|
+
count = Compact.decode(scale_bytes).value
|
73
|
+
value_bytes = scale_bytes.get_next_bytes(count)
|
74
|
+
end
|
75
|
+
|
76
|
+
### children decoding
|
77
|
+
children = []
|
78
|
+
bitmap = U16.decode(scale_bytes).value
|
79
|
+
NIBBLE_LENGTH.times do |i|
|
80
|
+
has_child = (bitmap & (1 << i)) != 0
|
81
|
+
children[i] = nil
|
82
|
+
if has_child
|
83
|
+
count = Compact.decode(scale_bytes).value
|
84
|
+
if count == 32
|
85
|
+
h = H256.decode(scale_bytes).value
|
86
|
+
children[i] = h
|
87
|
+
else
|
88
|
+
inline = scale_bytes.get_next_bytes count
|
89
|
+
children[i] = inline.bytes_to_hex
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
# debug
|
94
|
+
# children.each_with_index do |child, i|
|
95
|
+
# if child.nil?
|
96
|
+
# puts "#{i}: NULL"
|
97
|
+
# else
|
98
|
+
# puts "#{i}: #{child}"
|
99
|
+
# end
|
100
|
+
# end
|
101
|
+
|
102
|
+
result = TrieNode.new({
|
103
|
+
hash: hash,
|
104
|
+
node_type: "branch",
|
105
|
+
partial: {
|
106
|
+
hex: partial_bytes.bytes_to_hex,
|
107
|
+
padding: padding
|
108
|
+
},
|
109
|
+
children: children
|
110
|
+
})
|
111
|
+
|
112
|
+
result[:value] = value_bytes.bytes_to_hex if value_bytes
|
113
|
+
|
114
|
+
return result
|
115
|
+
else
|
116
|
+
puts "Not support"
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.check(root, proof, key)
|
123
|
+
key = Key.new(key)
|
124
|
+
|
125
|
+
nodes = proof.map {|node_data|
|
126
|
+
node = TrieNode::decode(Scale::Bytes.new(node_data)).value
|
127
|
+
[node[:hash], node]
|
128
|
+
}.to_h
|
129
|
+
|
130
|
+
self.do_check(root, nodes, key)
|
131
|
+
end
|
132
|
+
|
133
|
+
private
|
134
|
+
def self.do_check(hash, nodes, key)
|
135
|
+
if node = nodes[hash]
|
136
|
+
if node[:children]
|
137
|
+
position = key.next_nibble(node[:partial][:hex], node[:partial][:padding]).to_i(16)
|
138
|
+
child = node[:children][position]
|
139
|
+
return self.do_check(child, nodes, key)
|
140
|
+
else
|
141
|
+
return node[:value]
|
142
|
+
end
|
143
|
+
else
|
144
|
+
raise "Fail"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
class Key
|
153
|
+
def initialize(value)
|
154
|
+
@value = value[2..] if value.start_with?("0x")
|
155
|
+
@offset = 0
|
156
|
+
end
|
157
|
+
|
158
|
+
def next_nibble(partial, padding)
|
159
|
+
partial = partial[2..] if partial.start_with?("0x")
|
160
|
+
partial = partial[1..] if padding
|
161
|
+
|
162
|
+
new_offset = @offset + partial.length
|
163
|
+
if partial == @value[@offset...new_offset]
|
164
|
+
nibble = @value[new_offset]
|
165
|
+
@offset = new_offset + 1
|
166
|
+
return nibble
|
167
|
+
else
|
168
|
+
raise "Fail"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
data/lib/scale/types.rb
CHANGED
@@ -225,59 +225,71 @@ module Scale
|
|
225
225
|
end
|
226
226
|
end
|
227
227
|
|
228
|
-
class
|
228
|
+
class GenericAddress
|
229
229
|
include SingleValue
|
230
|
-
attr_accessor :account_length, :account_index, :account_id, :account_idx
|
231
230
|
|
231
|
+
# https://github.com/paritytech/substrate/wiki/External-Address-Format-(SS58)
|
232
|
+
# base58encode ( concat ( <address-type>, <address>, <checksum> ) )
|
233
|
+
# ^^^^^^^^^
|
234
|
+
# the <address> is 32 byte account id or 1, 2, 4, 8 byte account index
|
235
|
+
# scale_bytes: account length byte + <address>'s bytes
|
232
236
|
def self.decode(scale_bytes)
|
233
237
|
account_length = scale_bytes.get_next_bytes(1).first
|
234
238
|
|
235
|
-
if account_length == 0xff #
|
239
|
+
if account_length == 0xff # 32 bytes address(Public key)
|
236
240
|
account_id = scale_bytes.get_next_bytes(32).bytes_to_hex
|
237
|
-
account_length = account_length.
|
238
|
-
|
241
|
+
account_length = [account_length].bytes_to_hex
|
242
|
+
|
243
|
+
Address.new({
|
244
|
+
account_id: account_id,
|
245
|
+
account_length: account_length
|
246
|
+
})
|
239
247
|
else
|
240
248
|
account_index =
|
241
|
-
if account_length == 0xfc
|
249
|
+
if account_length == 0xfc # 2 bytes address(account index)
|
242
250
|
scale_bytes.get_next_bytes(2).bytes_to_hex
|
243
|
-
elsif account_length == 0xfd
|
251
|
+
elsif account_length == 0xfd # 4 bytes address(account index)
|
244
252
|
scale_bytes.get_next_bytes(4).bytes_to_hex
|
245
|
-
elsif account_length == 0xfe
|
253
|
+
elsif account_length == 0xfe # 8 bytes address(account index)
|
246
254
|
scale_bytes.get_next_bytes(8).bytes_to_hex
|
247
255
|
else
|
248
256
|
[account_length].bytes_to_hex
|
249
257
|
end
|
250
|
-
# account_idx
|
251
|
-
account_length = account_length.
|
252
|
-
|
258
|
+
# TODO: add account_idx
|
259
|
+
account_length = [account_length].bytes_to_hex
|
260
|
+
|
261
|
+
Address.new({
|
262
|
+
account_index: account_index,
|
263
|
+
account_length: account_length
|
264
|
+
})
|
253
265
|
end
|
254
266
|
end
|
255
267
|
|
256
|
-
def encode
|
257
|
-
if value
|
258
|
-
|
259
|
-
::Address.encode(value, addr_type)
|
260
|
-
else
|
261
|
-
prefix = if value.length == 66
|
262
|
-
"ff"
|
263
|
-
elsif value.length == 6
|
264
|
-
"fc"
|
265
|
-
elsif value.length == 10
|
266
|
-
"fd"
|
267
|
-
elsif value.length == 18
|
268
|
-
"fe"
|
269
|
-
else
|
270
|
-
""
|
271
|
-
end
|
272
|
-
"#{prefix}#{value[2..]}"
|
273
|
-
end
|
268
|
+
def encode
|
269
|
+
if self.value[:account_id]
|
270
|
+
"#{self.value[:account_length][2..]}#{self.value[:account_id][2..]}"
|
274
271
|
else
|
275
|
-
|
272
|
+
"#{self.value[:account_length][2..]}#{self.value[:account_index][2..]}"
|
276
273
|
end
|
277
274
|
end
|
278
275
|
end
|
279
276
|
|
280
|
-
class
|
277
|
+
class Address < GenericAddress; end
|
278
|
+
|
279
|
+
class RawAddress < GenericAddress; end
|
280
|
+
|
281
|
+
class AccountIdAddress < GenericAddress
|
282
|
+
def self.decode(scale_bytes)
|
283
|
+
AccountIdAddress.new({
|
284
|
+
account_id: AccountId.decode(scale_bytes).value,
|
285
|
+
account_length: "0xff"
|
286
|
+
})
|
287
|
+
end
|
288
|
+
|
289
|
+
def encode
|
290
|
+
"ff#{self.value[:account_id][2..]}"
|
291
|
+
end
|
292
|
+
end
|
281
293
|
|
282
294
|
class AccountId < H256; end
|
283
295
|
|
@@ -558,34 +570,52 @@ module Scale
|
|
558
570
|
|
559
571
|
class VecU8Length2
|
560
572
|
include VecU8FixedLength
|
573
|
+
BYTE_LENGTH = 2
|
561
574
|
end
|
562
575
|
|
563
576
|
class VecU8Length3
|
564
577
|
include VecU8FixedLength
|
578
|
+
BYTE_LENGTH = 3
|
565
579
|
end
|
566
580
|
|
567
581
|
class VecU8Length4
|
568
582
|
include VecU8FixedLength
|
583
|
+
BYTE_LENGTH = 4
|
569
584
|
end
|
570
585
|
|
571
586
|
class VecU8Length8
|
572
587
|
include VecU8FixedLength
|
588
|
+
BYTE_LENGTH = 8
|
573
589
|
end
|
574
590
|
|
575
591
|
class VecU8Length16
|
576
592
|
include VecU8FixedLength
|
593
|
+
BYTE_LENGTH = 16
|
577
594
|
end
|
578
595
|
|
579
596
|
class VecU8Length20
|
580
597
|
include VecU8FixedLength
|
598
|
+
BYTE_LENGTH = 20
|
581
599
|
end
|
582
600
|
|
583
601
|
class VecU8Length32
|
584
602
|
include VecU8FixedLength
|
603
|
+
BYTE_LENGTH = 32
|
585
604
|
end
|
586
605
|
|
587
606
|
class VecU8Length64
|
588
607
|
include VecU8FixedLength
|
608
|
+
BYTE_LENGTH = 64
|
609
|
+
end
|
610
|
+
|
611
|
+
class VecU8Length128
|
612
|
+
include VecU8FixedLength
|
613
|
+
BYTE_LENGTH = 128
|
614
|
+
end
|
615
|
+
|
616
|
+
class VecU8Length256
|
617
|
+
include VecU8FixedLength
|
618
|
+
BYTE_LENGTH = 256
|
589
619
|
end
|
590
620
|
|
591
621
|
class BalanceLock
|
data/lib/scale/version.rb
CHANGED
@@ -0,0 +1,159 @@
|
|
1
|
+
|
2
|
+
def ws_request(url, payload)
|
3
|
+
result = nil
|
4
|
+
Kontena::Websocket::Client.connect(url, {}) do |client|
|
5
|
+
client.send(payload.to_json)
|
6
|
+
|
7
|
+
client.read do |message|
|
8
|
+
result = JSON.parse message
|
9
|
+
client.close(1000)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
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
|
18
|
+
end
|
19
|
+
|
20
|
+
class SubstrateClient
|
21
|
+
class WebsocketError < StandardError; end
|
22
|
+
class RpcError < StandardError; end
|
23
|
+
class RpcTimeout < StandardError; end
|
24
|
+
|
25
|
+
attr_reader :metadata
|
26
|
+
attr_reader :spec_name, :spec_version
|
27
|
+
|
28
|
+
def initialize(url)
|
29
|
+
@url = url
|
30
|
+
@request_id = 1
|
31
|
+
@metadata_cache = {}
|
32
|
+
end
|
33
|
+
|
34
|
+
def request(method, params)
|
35
|
+
payload = {
|
36
|
+
"jsonrpc" => "2.0",
|
37
|
+
"method" => method,
|
38
|
+
"params" => params,
|
39
|
+
"id" => @request_id
|
40
|
+
}
|
41
|
+
|
42
|
+
data = ws_request(@url, payload)
|
43
|
+
if data["error"]
|
44
|
+
raise RpcError, data["error"]
|
45
|
+
else
|
46
|
+
data["result"]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def init_types_and_metadata(block_hash=nil)
|
51
|
+
runtime_version = self.state_getRuntimeVersion(block_hash)
|
52
|
+
spec_name = runtime_version["specName"].downcase
|
53
|
+
spec_version = runtime_version["specVersion"]
|
54
|
+
|
55
|
+
registry = Scale::TypeRegistry.instance
|
56
|
+
|
57
|
+
# load types
|
58
|
+
if registry.types == nil
|
59
|
+
registry.load(spec_name: spec_name)
|
60
|
+
end
|
61
|
+
registry.spec_version = spec_version
|
62
|
+
|
63
|
+
# set current metadata
|
64
|
+
metadata = @metadata_cache[spec_version]
|
65
|
+
if metadata.nil?
|
66
|
+
hex = self.state_getMetadata(block_hash)
|
67
|
+
metadata = Scale::Types::Metadata.decode(Scale::Bytes.new(hex))
|
68
|
+
@metadata_cache[spec_version] = metadata
|
69
|
+
end
|
70
|
+
|
71
|
+
@metadata = metadata
|
72
|
+
registry.metadata = metadata
|
73
|
+
|
74
|
+
true
|
75
|
+
end
|
76
|
+
|
77
|
+
def get_metadata_from_cache(spec_version)
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
def invoke(method, *params)
|
82
|
+
request(method, params)
|
83
|
+
end
|
84
|
+
|
85
|
+
# ################################################
|
86
|
+
# origin rpc methods
|
87
|
+
# ################################################
|
88
|
+
def method_missing(method, *args)
|
89
|
+
invoke method, *args
|
90
|
+
end
|
91
|
+
|
92
|
+
# ################################################
|
93
|
+
# custom methods based on origin rpc methods
|
94
|
+
# ################################################
|
95
|
+
def methods
|
96
|
+
invoke("rpc_methods")["methods"]
|
97
|
+
end
|
98
|
+
|
99
|
+
def get_block_number(block_hash)
|
100
|
+
header = self.chain_getHeader(block_hash)
|
101
|
+
header["number"].to_i(16)
|
102
|
+
end
|
103
|
+
|
104
|
+
def get_metadata(block_hash=nil)
|
105
|
+
self.init_types_and_metadata(block_hash)
|
106
|
+
@metadata
|
107
|
+
end
|
108
|
+
|
109
|
+
def get_block(block_hash=nil)
|
110
|
+
self.init_types_and_metadata(block_hash)
|
111
|
+
block = self.chain_getBlock(block_hash)
|
112
|
+
SubstrateClient::Helper.decode_block(block)
|
113
|
+
rescue => ex
|
114
|
+
puts ex.message
|
115
|
+
puts ex.backtrace.join("\n\t")
|
116
|
+
end
|
117
|
+
|
118
|
+
def get_block_events(block_hash=nil)
|
119
|
+
self.init_types_and_metadata(block_hash)
|
120
|
+
|
121
|
+
storage_key = "0x26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7"
|
122
|
+
events_data = state_getStorage storage_key, block_hash
|
123
|
+
|
124
|
+
scale_bytes = Scale::Bytes.new(events_data)
|
125
|
+
Scale::Types.get("Vec<EventRecord>").decode(scale_bytes).to_human
|
126
|
+
end
|
127
|
+
|
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
|
+
def get_storage(module_name, storage_name, params = nil, block_hash = nil)
|
133
|
+
self.init_types_and_metadata(block_hash)
|
134
|
+
|
135
|
+
storage_key, return_type = SubstrateClient::Helper.generate_storage_key_from_metadata(@metadata, module_name, storage_name, params)
|
136
|
+
data = self.state_getStorage(storage_key, block_hash)
|
137
|
+
return unless data
|
138
|
+
|
139
|
+
bytes = Scale::Bytes.new(data)
|
140
|
+
type = Scale::Types.get(return_type)
|
141
|
+
type.decode(bytes)
|
142
|
+
end
|
143
|
+
|
144
|
+
def generate_storage_key(module_name, storage_name, params = nil, block_hash = nil)
|
145
|
+
self.init_types_and_metadata(block_hash)
|
146
|
+
SubstrateClient::Helper.generate_storage_key_from_metadata(@metadata, module_name, storage_name, params)
|
147
|
+
end
|
148
|
+
|
149
|
+
# compose_call "Balances", "Transfer", { dest: "0x586cb27c291c813ce74e86a60dad270609abf2fc8bee107e44a80ac00225c409", value: 1_000_000_000_000 }
|
150
|
+
def compose_call(module_name, call_name, params, block_hash=nil)
|
151
|
+
self.init_types_and_metadata(block_hash)
|
152
|
+
SubstrateClient::Helper.compose_call_from_metadata(@metadata, module_name, call_name, params)
|
153
|
+
end
|
154
|
+
|
155
|
+
def generate_storage_hash_from_data(storage_hex_data)
|
156
|
+
"0x" + Crypto.blake2_256(Scale::Bytes.new(storage_hex_data).bytes)
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|