ton-sdk-ruby 0.0.1

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.
@@ -0,0 +1,428 @@
1
+ require 'pp'
2
+
3
+ module TonSdkRuby
4
+ extend TonSdkRuby
5
+
6
+ REACH_BOC_MAGIC_PREFIX = hex_to_bytes('B5EE9C72')
7
+ LEAN_BOC_MAGIC_PREFIX = hex_to_bytes('68FF65F3')
8
+ LEAN_BOC_MAGIC_PREFIX_CRC = hex_to_bytes('ACC3A728')
9
+
10
+ class BOCOptions
11
+ attr_accessor :has_index, :hash_crc32, :has_cache_bits, :topological_order, :flags
12
+ end
13
+
14
+ class BocHeader
15
+ attr_accessor :has_index, :hash_crc32, :has_cache_bits, :flags, :size_bytes,
16
+ :offset_bytes, :cells_num, :roots_num, :absent_num,
17
+ :tot_cells_size, :root_list, :cells_data
18
+ end
19
+
20
+ class CellNode
21
+ attr_accessor :cell, :children, :scanned
22
+ end
23
+
24
+ class BuilderNode
25
+ attr_accessor :builder, :indent
26
+ end
27
+
28
+ class CellPointer
29
+ attr_accessor :cell, :type, :builder, :refs
30
+ end
31
+
32
+ class CellData
33
+ attr_accessor :pointer, :remainder
34
+ end
35
+
36
+ def deserialize_fift(data)
37
+ raise 'Can\'t deserialize. Empty fift hex.' if data.nil? || data.empty?
38
+
39
+ re = /((\s*)x{([0-9a-zA-Z_]+)}\n?)/mi
40
+ matches = data.scan(re) || []
41
+
42
+ raise 'Can\'t deserialize. Bad fift hex.' if matches.empty?
43
+
44
+ parse_fift_hex = lambda do |fift|
45
+ return [] if fift == '_'
46
+
47
+ bits = fift.split('')
48
+ .map { |el| el == '_' ? el : hex_to_bits(el).join('') }
49
+ .join('')
50
+ .sub(/1[0]*_$/, '')
51
+ .split('')
52
+ .map(&:to_i)
53
+
54
+ bits
55
+ end
56
+
57
+ if matches.length == 1
58
+ return [Cell.new(bits: parse_fift_hex.call(matches[0][2]))]
59
+ end
60
+
61
+ is_last_nested = lambda do |stack, indent|
62
+ last_stack_indent = stack[-1][:indent]
63
+ last_stack_indent != 0 && last_stack_indent >= indent
64
+ end
65
+
66
+ stack = matches.each_with_object([]).with_index do |(el, acc), i|
67
+ _, spaces, fift = el
68
+ is_last = i == matches.length - 1
69
+ indent = spaces.length
70
+ bits = parse_fift_hex.call(fift)
71
+ builder = Builder.new.store_bits(bits)
72
+
73
+ while !acc.empty? && is_last_nested.call(acc, indent)
74
+ b = acc.pop[:builder]
75
+ acc[-1][:builder].store_ref(b.cell)
76
+ end
77
+
78
+ if is_last
79
+ acc[-1][:builder].store_ref(builder.cell)
80
+ else
81
+ acc.push(indent: indent, builder: builder)
82
+ end
83
+ end
84
+
85
+ stack.map { |el| el[:builder].cell }
86
+ end
87
+
88
+
89
+ def deserialize_header(bytes)
90
+ raise 'Not enough bytes for magic prefix' if bytes.length < 4 + 1
91
+
92
+ crcbytes = bytes[0, bytes.length - 4]
93
+ prefix = bytes.shift(4)
94
+ flags_byte = bytes.shift
95
+ header = {
96
+ has_index: true,
97
+ hash_crc32: nil,
98
+ has_cache_bits: false,
99
+ flags: 0,
100
+ size_bytes: flags_byte,
101
+ offset_bytes: nil,
102
+ cells_num: nil,
103
+ roots_num: nil,
104
+ absent_num: nil,
105
+ tot_cells_size: nil,
106
+ root_list: nil,
107
+ cells_data: nil
108
+ }
109
+
110
+ if bytes_compare(prefix, REACH_BOC_MAGIC_PREFIX)
111
+ header[:has_index] = (flags_byte & 128) != 0
112
+ header[:has_cache_bits] = (flags_byte & 32) != 0
113
+ header[:flags] = (flags_byte & 16) * 2 + (flags_byte & 8)
114
+ header[:size_bytes] = flags_byte % 8
115
+ header[:hash_crc32] = flags_byte & 64
116
+ elsif bytes_compare(prefix, LEAN_BOC_MAGIC_PREFIX)
117
+ header[:hash_crc32] = 0
118
+ elsif bytes_compare(prefix, LEAN_BOC_MAGIC_PREFIX_CRC)
119
+ header[:hash_crc32] = 1
120
+ else
121
+ raise 'Bad magic prefix'
122
+ end
123
+
124
+ raise 'Not enough bytes for encoding cells counters' if bytes.length < 1 + 5 * header[:size_bytes]
125
+
126
+ offset_bytes = bytes.shift
127
+ header[:offset_bytes] = offset_bytes
128
+ header[:cells_num] = bytes_to_uint(bytes.shift(header[:size_bytes]))
129
+ header[:roots_num] = bytes_to_uint(bytes.shift(header[:size_bytes]))
130
+ header[:absent_num] = bytes_to_uint(bytes.shift(header[:size_bytes]))
131
+ header[:tot_cells_size] = bytes_to_uint(bytes.shift(offset_bytes))
132
+
133
+ raise 'Not enough bytes for encoding root cells hashes' if bytes.length < header[:roots_num] * header[:size_bytes]
134
+
135
+ header[:root_list] = Array.new(header[:roots_num]) do
136
+ ref_index = bytes_to_uint(bytes.shift(header[:size_bytes]))
137
+ ref_index
138
+ end
139
+
140
+ if header[:has_index]
141
+ raise 'Not enough bytes for index encoding' if bytes.length < header[:offset_bytes] * header[:cells_num]
142
+ Array.new(header[:cells_num]) { bytes.shift(header[:offset_bytes]) }
143
+ end
144
+
145
+ raise 'Not enough bytes for cells data' if bytes.length < header[:tot_cells_size]
146
+ # byebug
147
+ header[:cells_data] = bytes.shift(header[:tot_cells_size])
148
+
149
+ if header[:hash_crc32]
150
+ raise 'Not enough bytes for crc32c hashsum' if bytes.length < 4
151
+
152
+ result = crc32c_bytes_le(crcbytes)
153
+
154
+ raise 'Crc32c hashsum mismatch' unless bytes_compare(result, bytes.shift(4))
155
+ end
156
+
157
+ raise 'Too much bytes in BoC serialization' unless bytes.empty?
158
+
159
+ header
160
+ end
161
+
162
+ def deserialize_cell(remainder, ref_index_size)
163
+ raise "BoC not enough bytes to encode cell descriptors" if remainder.length < 2
164
+
165
+ refs_descriptor = remainder.shift
166
+ level = refs_descriptor >> 5
167
+ total_refs = refs_descriptor & 7
168
+ has_hashes = (refs_descriptor & 16) != 0
169
+ is_exotic = (refs_descriptor & 8) != 0
170
+ is_absent = total_refs == 7 && has_hashes
171
+
172
+ # For absent cells (i.e., external references), only refs descriptor is present
173
+ # Currently not implemented
174
+ if is_absent
175
+ raise "BoC can't deserialize absent cell"
176
+ end
177
+
178
+ raise "BoC cell can't has more than 4 refs #{total_refs}" if total_refs > 4
179
+
180
+ bits_descriptor = remainder.shift
181
+ is_augmented = (bits_descriptor & 1) != 0
182
+ data_size = (bits_descriptor >> 1) + (is_augmented ? 1 : 0)
183
+ hashes_size = has_hashes ? (level + 1) * 32 : 0
184
+ depth_size = has_hashes ? (level + 1) * 2 : 0
185
+
186
+ required_bytes = hashes_size + depth_size + data_size + ref_index_size * total_refs
187
+
188
+ raise "BoC not enough bytes to encode cell data" if remainder.length < required_bytes
189
+
190
+ remainder.shift(hashes_size + depth_size) if has_hashes
191
+
192
+ bits = if is_augmented
193
+ rollback(bytes_to_bits(remainder.shift(data_size)))
194
+ else
195
+ bytes_to_bits(remainder.shift(data_size))
196
+ end
197
+
198
+ raise "BoC not enough bytes for an exotic cell type" if is_exotic && bits.length < 8
199
+
200
+ type = if is_exotic
201
+ bits_to_int_uint(bits[0, 8], { type: "int" })
202
+ else
203
+ CellType::Ordinary
204
+ end
205
+
206
+ raise "BoC an exotic cell can't be of ordinary type" if is_exotic && type == CellType::Ordinary
207
+
208
+ pointer = {
209
+ type: type,
210
+ builder: Builder.new(bits.length).store_bits(bits),
211
+ refs: Array.new(total_refs) { bytes_to_uint(remainder.shift(ref_index_size)) }
212
+ }
213
+
214
+ { pointer: pointer, remainder: remainder }
215
+ end
216
+
217
+ def deserialize(data, check_merkle_proofs = false)
218
+ has_merkle_proofs = false
219
+ bytes = Array.new(data)
220
+ pointers = []
221
+ header = deserialize_header(bytes)
222
+
223
+ header[:cells_num].times do
224
+ deserialized = deserialize_cell(header[:cells_data], header[:size_bytes])
225
+ header[:cells_data] = deserialized[:remainder]
226
+ pointers.push(deserialized[:pointer])
227
+ end
228
+
229
+ pointers.reverse_each.with_index do |pointer, i|
230
+ pointer_index = pointers.length - i - 1
231
+ cell_builder = pointer[:builder]
232
+ cell_type = pointer[:type]
233
+
234
+ pointer[:refs].each do |ref_index|
235
+ ref_builder = pointers[ref_index][:builder]
236
+ ref_type = pointers[ref_index][:type]
237
+
238
+ raise "Topological order is broken" if ref_index < pointer_index
239
+
240
+ if ref_type == CellType::MerkleProof || ref_type == CellType::MerkleUpdate
241
+ has_merkle_proofs = true
242
+ end
243
+
244
+ cell_builder.store_ref(ref_builder.cell(ref_type))
245
+ end
246
+
247
+ if cell_type == CellType::MerkleProof || cell_type == CellType::MerkleUpdate
248
+ has_merkle_proofs = true
249
+ end
250
+
251
+ pointers[pointer_index][:cell] = cell_builder.cell(cell_type)
252
+ end
253
+
254
+ raise "BOC does not contain Merkle Proofs" if check_merkle_proofs && !has_merkle_proofs
255
+
256
+ header[:root_list].map { |ref_index| pointers[ref_index][:cell] }
257
+ end
258
+
259
+ def depth_first_sort(root)
260
+ stack = [{
261
+ cell: Cell.new(refs: root),
262
+ children: root.length,
263
+ scanned: 0
264
+ }]
265
+
266
+ cells = []
267
+ hash_indexes = {}
268
+
269
+ process = lambda do |node|
270
+ ref = node[:cell].refs[node[:scanned]]
271
+ hash = ref.hash
272
+ index = hash_indexes[hash]
273
+ length = index.nil? ? cells.push(cell: ref, hash: hash) : cells.push(cells.delete_at(index))
274
+
275
+ stack.push(cell: ref, children: ref.refs.length, scanned: 0)
276
+ hash_indexes[hash] = length - 1
277
+ end
278
+
279
+ while !stack.empty?
280
+ current = stack.last
281
+
282
+ if current[:children] != current[:scanned]
283
+ process.call(current)
284
+ else
285
+ while !stack.empty? && current && current[:children] == current[:scanned]
286
+ stack.pop
287
+ current = stack.last
288
+ end
289
+
290
+ process.call(current) if current
291
+ end
292
+ end
293
+
294
+ result = cells.each_with_index.reduce({ cells: [], hashmap: {} }) do |acc, (el, i)|
295
+ unless el.nil?
296
+ acc[:cells].push(el[:cell])
297
+ acc[:hashmap][el[:hash]] = i
298
+ end
299
+
300
+ acc
301
+ end
302
+
303
+ result
304
+ end
305
+
306
+ def breadth_first_sort(root)
307
+ stack = Array.new(root)
308
+ cells = root.map { |el| { cell: el, hash: el.hash } }
309
+ hash_indexes = cells.map.with_index { |el, i| [el[:hash], i] }.to_h
310
+
311
+ while !stack.empty?
312
+ length = stack.length
313
+
314
+ stack.each do |node|
315
+ node.refs.each do |ref|
316
+ hash = ref.hash
317
+ index = hash_indexes[hash]
318
+ length = index.nil? ? cells.push(cell: ref, hash: hash).size : cells.push(cells.delete_at(index)).size
319
+
320
+ stack.push(ref)
321
+ hash_indexes[hash] = length - 1
322
+ end
323
+ end
324
+
325
+ stack.shift(length)
326
+ end
327
+
328
+ result = cells.each_with_index.reduce({ cells: [], hashmap: {} }) do |acc, (el, i)|
329
+ unless el.nil?
330
+ acc[:cells].push(el[:cell])
331
+ acc[:hashmap][el[:hash]] = i
332
+ end
333
+
334
+ acc
335
+ end
336
+
337
+ result
338
+ end
339
+
340
+ def serialize_cell(cell, hashmap, ref_index_size)
341
+ representation = cell.get_refs_descriptor +
342
+ cell.get_bits_descriptor +
343
+ cell.get_augmented_bits
344
+
345
+ serialized = cell.refs.reduce(representation) do |acc, ref|
346
+ ref_index = hashmap[ref.hash]
347
+ bits = Array.new(ref_index_size) { |i| (ref_index >> i) & 1 }
348
+
349
+ acc + bits.reverse
350
+ end
351
+
352
+ serialized
353
+ end
354
+
355
+
356
+ def serialize(root, options = {})
357
+ root = [*root]
358
+ # TODO: Implement breadthFirstSort and depthFirstSort functions
359
+
360
+ has_index = options.fetch(:has_index, false)
361
+ has_cache_bits = options.fetch(:has_cache_bits, false)
362
+ hash_crc32 = options.fetch(:hash_crc32, true)
363
+ topological_order = options.fetch(:topological_order, 'breadth-first')
364
+ flags = options.fetch(:flags, 0)
365
+
366
+ if topological_order == 'breadth-first'
367
+ breadth = breadth_first_sort(root)
368
+ cells_list = breadth[:cells]
369
+ hashmap = breadth[:hashmap]
370
+ else
371
+ breadth = depth_first_sort(root)
372
+ cells_list = breadth[:cells]
373
+ hashmap = breadth[:hashmap]
374
+ end
375
+
376
+ cells_num = cells_list.size
377
+ size = cells_num.to_s(2).size
378
+ size_bytes = [(size.to_f / 8).ceil, 1].max
379
+ cells_bits = []
380
+ size_index = []
381
+
382
+ cells_list.each do |cell|
383
+ bits = serialize_cell(cell, hashmap, size_bytes * 8)
384
+ cells_bits.concat(bits)
385
+ size_index.push(bits.length / 8)
386
+ end
387
+
388
+ full_size = cells_bits.length / 8
389
+ offset_bits = full_size.to_s(2).length
390
+ offset_bytes = [(offset_bits / 8.0).ceil, 1].max
391
+ builder_size = (32 + 3 + 2 + 3 + 8) +
392
+ (cells_bits.length) +
393
+ ((size_bytes * 8) * 4) +
394
+ (offset_bytes * 8) +
395
+ (has_index ? (cells_list.length * (offset_bytes * 8)) : 0)
396
+
397
+ result = Builder.new(builder_size)
398
+
399
+ result.store_bytes(REACH_BOC_MAGIC_PREFIX)
400
+ .store_bit(has_index ? 1 : 0)
401
+ .store_bit(hash_crc32 ? 1 : 0)
402
+ .store_bit(has_cache_bits ? 1 : 0)
403
+ .store_uint(flags, 2)
404
+ .store_uint(size_bytes, 3)
405
+ .store_uint(offset_bytes, 8)
406
+ .store_uint(cells_num, size_bytes * 8)
407
+ .store_uint(root.length, size_bytes * 8)
408
+ .store_uint(0, size_bytes * 8)
409
+ .store_uint(full_size, offset_bytes * 8)
410
+ .store_uint(0, size_bytes * 8)
411
+
412
+ if has_index
413
+ size_index.each do |index|
414
+ result.store_uint(index, offset_bytes * 8)
415
+ end
416
+ end
417
+
418
+ augmented_bits = augment(result.store_bits(cells_bits).bits)
419
+ bytes = bits_to_bytes(augmented_bits)
420
+
421
+ if hash_crc32
422
+ hashsum = crc32c_bytes_le(bytes)
423
+ bytes + hashsum
424
+ else
425
+ bytes
426
+ end
427
+ end
428
+ end