scale.rb 0.2.9 → 0.2.15
Sign up to get free protection for your applications and to get access to all the features.
- 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
|