/__w/smoldot/smoldot/repo/lib/src/database/full_sqlite/tests.rs
Line | Count | Source (jump to first uncovered line) |
1 | | // Smoldot |
2 | | // Copyright (C) 2023 Pierre Krieger |
3 | | // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 |
4 | | |
5 | | // This program is free software: you can redistribute it and/or modify |
6 | | // it under the terms of the GNU General Public License as published by |
7 | | // the Free Software Foundation, either version 3 of the License, or |
8 | | // (at your option) any later version. |
9 | | |
10 | | // This program is distributed in the hope that it will be useful, |
11 | | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | | // GNU General Public License for more details. |
14 | | |
15 | | // You should have received a copy of the GNU General Public License |
16 | | // along with this program. If not, see <http://www.gnu.org/licenses/>. |
17 | | |
18 | | #![cfg(test)] |
19 | | |
20 | | use super::{ |
21 | | open, Config, ConfigTy, DatabaseOpen, InsertTrieNode, InsertTrieNodeStorageValue, |
22 | | StorageAccessError, |
23 | | }; |
24 | | use crate::{header, trie}; |
25 | | |
26 | | use alloc::borrow::Cow; |
27 | | use core::{array, iter}; |
28 | | use rand::distributions::{Distribution as _, Uniform}; |
29 | | |
30 | | #[test] |
31 | 1 | fn empty_database_fill_then_query() { |
32 | | // Repeat the test many times due to randomness. |
33 | 1.02k | for _ in 0..1024 { |
34 | 1.02k | let DatabaseOpen::Empty(empty_db) = open(Config { |
35 | 1.02k | block_number_bytes: 4, |
36 | 1.02k | cache_size: 2 * 1024 * 1024, |
37 | 1.02k | ty: ConfigTy::Memory, |
38 | 1.02k | }) |
39 | 1.02k | .unwrap() else { |
40 | 0 | panic!() |
41 | | }; |
42 | | |
43 | 18.9M | fn uniform_sample(min: u8, max: u8) -> u8 { |
44 | 18.9M | Uniform::new_inclusive(min, max).sample(&mut rand::thread_rng()) |
45 | 18.9M | } |
46 | | |
47 | | // Create a random trie. |
48 | | // Each node contains a `Some` with the storage value or `None` for branch nodes, plus its |
49 | | // Merkle value as `Some` if already calculated. |
50 | 1.02k | let mut trie = trie::trie_structure::TrieStructure::<( |
51 | 1.02k | Option<Vec<u8>>, |
52 | 1.02k | Option<trie::trie_node::MerkleValueOutput>, |
53 | 1.02k | )>::new(); |
54 | 1.02k | |
55 | 1.02k | let mut list = vec![Vec::new()]; |
56 | 1.02k | for elem in list.clone().into_iter() { |
57 | 1.02k | for _ in 0..uniform_sample(0, 4) { |
58 | 1.98k | let mut elem = elem.clone(); |
59 | 2.94k | for _ in 0..uniform_sample(0, 3) { |
60 | 2.94k | elem.push(uniform_sample(0, 255)); |
61 | 2.94k | } |
62 | 1.98k | list.push(elem); |
63 | | } |
64 | | } |
65 | 4.03k | for elem3.01k in list { |
66 | 3.01k | let mut storage_value = Vec::new(); |
67 | 36.0k | for _ in 0..uniform_sample(0, 24) { |
68 | 36.0k | storage_value.push(uniform_sample(0, 255)); |
69 | 36.0k | } |
70 | | |
71 | 3.01k | match trie.node(trie::bytes_to_nibbles(elem.iter().copied())) { |
72 | 2.51k | trie::trie_structure::Entry::Vacant(e) => { |
73 | 2.51k | e.insert_storage_value() |
74 | 2.51k | .insert((Some(storage_value), None), (None, None)); |
75 | 2.51k | } |
76 | | trie::trie_structure::Entry::Occupied( |
77 | 0 | trie::trie_structure::NodeAccess::Branch(mut e), |
78 | 0 | ) => { |
79 | 0 | *e.user_data() = (Some(storage_value), None); |
80 | 0 | e.insert_storage_value(); |
81 | 0 | } |
82 | | trie::trie_structure::Entry::Occupied( |
83 | | trie::trie_structure::NodeAccess::Storage(_), |
84 | 496 | ) => {} |
85 | | } |
86 | | } |
87 | | |
88 | | // Calculate the Merkle values of the nodes of the trie. |
89 | 2.58k | for node_index in trie.iter_ordered().collect::<Vec<_>>().into_iter().rev()1.02k { |
90 | 2.58k | let mut node_access = trie.node_by_index(node_index).unwrap(); |
91 | 2.58k | |
92 | 41.3k | let children = core::array::from_fn::<_, 16, _>(|n| { |
93 | 41.3k | node_access |
94 | 41.3k | .child(trie::Nibble::try_from(u8::try_from(n).unwrap()).unwrap()) |
95 | 41.3k | .map(|mut child| child.user_data().1.as_ref().unwrap().clone()1.56k ) |
96 | 41.3k | }); |
97 | 2.58k | |
98 | 2.58k | let is_root_node = node_access.is_root_node(); |
99 | 2.58k | let partial_key = node_access.partial_key().collect::<Vec<_>>().into_iter(); |
100 | | |
101 | 2.58k | let storage_value = match node_access.user_data().0.as_ref() { |
102 | 2.51k | Some(v) => trie::trie_node::StorageValue::Unhashed(&v[..]), |
103 | 68 | None => trie::trie_node::StorageValue::None, |
104 | | }; |
105 | | |
106 | 2.58k | let merkle_value = trie::trie_node::calculate_merkle_value( |
107 | 2.58k | trie::trie_node::Decoded { |
108 | 2.58k | children, |
109 | 2.58k | partial_key, |
110 | 2.58k | storage_value, |
111 | 2.58k | }, |
112 | 2.58k | trie::HashFunction::Blake2, |
113 | 2.58k | is_root_node, |
114 | 2.58k | ) |
115 | 2.58k | .unwrap(); |
116 | 2.58k | |
117 | 2.58k | node_access.into_user_data().1 = Some(merkle_value); |
118 | | } |
119 | | |
120 | | // Store the trie in the database. |
121 | 1.02k | let open_db = { |
122 | 1.02k | let state_root = &trie |
123 | 1.02k | .root_user_data() |
124 | 1.02k | .map(|n| *<&[u8; 32]>::try_from(n.1.as_ref().unwrap().as_ref()).unwrap()) |
125 | 1.02k | .unwrap_or(trie::EMPTY_BLAKE2_TRIE_MERKLE_VALUE); |
126 | 1.02k | |
127 | 1.02k | let trie_entries_linear = |
128 | 1.02k | trie.iter_unordered() |
129 | 1.02k | .collect::<Vec<_>>() |
130 | 1.02k | .into_iter() |
131 | 2.58k | .map(|node_index| { |
132 | 2.58k | let (storage_value, Some(merkle_value)) = &trie[node_index] else { |
133 | 0 | unreachable!() |
134 | | }; |
135 | 2.58k | let storage_value = if let Some(storage_value2.51k ) = storage_value { |
136 | 2.51k | InsertTrieNodeStorageValue::Value { |
137 | 2.51k | value: Cow::Owned(storage_value.to_vec()), |
138 | 2.51k | references_merkle_value: false, // TODO: test this as well |
139 | 2.51k | } |
140 | | } else { |
141 | 68 | InsertTrieNodeStorageValue::NoValue |
142 | | }; |
143 | 2.58k | let merkle_value = merkle_value.as_ref().to_owned(); |
144 | 2.58k | let mut node_access = trie.node_by_index(node_index).unwrap(); |
145 | 2.58k | |
146 | 2.58k | InsertTrieNode { |
147 | 2.58k | storage_value, |
148 | 2.58k | merkle_value: Cow::Owned(merkle_value), |
149 | 41.3k | children_merkle_values: array::from_fn::<_, 16, _>(|n| { |
150 | 41.3k | let child_index = |
151 | 41.3k | trie::Nibble::try_from(u8::try_from(n).unwrap()).unwrap(); |
152 | 41.3k | if let Some(mut child1.56k ) = node_access.child(child_index) { |
153 | 1.56k | Some(Cow::Owned( |
154 | 1.56k | child.user_data().1.as_ref().unwrap().as_ref().to_vec(), |
155 | 1.56k | )) |
156 | | } else { |
157 | 39.7k | None |
158 | | } |
159 | 41.3k | }), |
160 | 2.58k | partial_key_nibbles: Cow::Owned( |
161 | 2.58k | node_access.partial_key().map(u8::from).collect::<Vec<_>>(), |
162 | 2.58k | ), |
163 | 2.58k | } |
164 | 2.58k | }); |
165 | 1.02k | |
166 | 1.02k | let db = empty_db |
167 | 1.02k | .initialize( |
168 | 1.02k | &header::HeaderRef { |
169 | 1.02k | number: 0, |
170 | 1.02k | extrinsics_root: &[0; 32], |
171 | 1.02k | parent_hash: &[0; 32], |
172 | 1.02k | state_root, |
173 | 1.02k | digest: header::DigestRef::empty(), |
174 | 1.02k | } |
175 | 1.02k | .scale_encoding_vec(4), |
176 | 1.02k | iter::empty(), |
177 | 1.02k | None, |
178 | 1.02k | ) |
179 | 1.02k | .unwrap(); |
180 | 1.02k | db.insert_trie_nodes(trie_entries_linear, 0).unwrap(); |
181 | 1.02k | db |
182 | 1.02k | }; |
183 | 1.02k | |
184 | 1.02k | let block0_hash = open_db.finalized_block_hash().unwrap(); |
185 | | |
186 | | // Ask random keys. |
187 | 1.04M | for _ in 0..1024 { |
188 | 1.04M | let key = (0..uniform_sample(0, 4)) |
189 | 2.09M | .map(|_| uniform_sample(0, 255)) |
190 | 1.04M | .collect::<Vec<_>>(); |
191 | 1.04M | let actual = open_db |
192 | 1.04M | .block_storage_get( |
193 | 1.04M | &block0_hash, |
194 | 1.04M | iter::empty::<iter::Empty<_>>(), |
195 | 1.04M | trie::bytes_to_nibbles(key.iter().copied()).map(u8::from), |
196 | 1.04M | ) |
197 | 1.04M | .unwrap(); |
198 | 1.04M | let expected = trie |
199 | 1.04M | .node_by_full_key(trie::bytes_to_nibbles(key.iter().copied())) |
200 | 1.04M | .and_then(|n| Some((trie[n].0.as_ref()209k ?2 .clone()209k , 0u8))209k ); |
201 | 1.04M | assert_eq!( |
202 | | actual, |
203 | | expected, |
204 | 0 | "\nkey = {:?}\ntrie = {:?}", |
205 | 0 | key.iter().map(|n| format!("{:x}", n)).collect::<String>(), |
206 | | trie |
207 | | ); |
208 | | } |
209 | | |
210 | | // Ask random next keys. |
211 | 1.04M | for _ in 0..1024 { |
212 | 1.04M | let key = (0..uniform_sample(0, 8)) |
213 | 4.19M | .map(|_| trie::Nibble::try_from(uniform_sample(0u8, 15)).unwrap()) |
214 | 1.04M | .collect::<Vec<_>>(); |
215 | 1.04M | let prefix = (0..uniform_sample(0, 8)) |
216 | 4.19M | .map(|_| trie::Nibble::try_from(uniform_sample(0u8, 15)).unwrap()) |
217 | 1.04M | .collect::<Vec<_>>(); |
218 | 1.04M | let branch_nodes = rand::random::<bool>(); |
219 | 1.04M | let actual = open_db |
220 | 1.04M | .block_storage_next_key( |
221 | 1.04M | &block0_hash, |
222 | 1.04M | iter::empty::<iter::Empty<_>>(), |
223 | 1.04M | key.iter().copied().map(u8::from), |
224 | 1.04M | prefix.iter().copied().map(u8::from), |
225 | 1.04M | branch_nodes, |
226 | 1.04M | ) |
227 | 1.04M | .unwrap(); |
228 | 1.04M | let expected = trie |
229 | 1.04M | .iter_ordered() |
230 | 2.20M | .filter(|n| branch_nodes || trie[*n].0.is_some(1.10M )) |
231 | 2.17M | .map(|n| trie.node_full_key_by_index(n).unwrap().collect::<Vec<_>>()) |
232 | 2.17M | .find(|n| *n >= key) |
233 | 1.04M | .filter(|n| n.starts_with(&prefix)549k ) |
234 | 1.04M | .map(|k| k.iter().copied().map(u8::from).collect::<Vec<_>>()64.3k ); |
235 | 1.04M | assert_eq!( |
236 | | actual, |
237 | | expected, |
238 | 0 | "\nkey = {:?}\nprefix = {:?}\nbranch_nodes = {:?}\ntrie = {:?}", |
239 | 0 | key.iter().map(|n| format!("{:x}", n)).collect::<String>(), |
240 | 0 | prefix |
241 | 0 | .iter() |
242 | 0 | .map(|n| format!("{:x}", n)) |
243 | 0 | .collect::<String>(), |
244 | | branch_nodes, |
245 | | trie |
246 | | ); |
247 | | } |
248 | | |
249 | | // Ask random closest descendant Merkle values. |
250 | 1.04M | for _ in 0..1024 { |
251 | 1.04M | let key = (0..uniform_sample(0, 8)) |
252 | 4.19M | .map(|_| trie::Nibble::try_from(uniform_sample(0u8, 15)).unwrap()) |
253 | 1.04M | .collect::<Vec<_>>(); |
254 | 1.04M | let actual = open_db |
255 | 1.04M | .block_storage_closest_descendant_merkle_value( |
256 | 1.04M | &block0_hash, |
257 | 1.04M | iter::empty::<iter::Empty<_>>(), |
258 | 1.04M | key.iter().copied().map(u8::from), |
259 | 1.04M | ) |
260 | 1.04M | .unwrap(); |
261 | 1.04M | let expected = trie |
262 | 1.04M | .iter_ordered() |
263 | 2.46M | .find(|n| { |
264 | 2.46M | let full_key = trie.node_full_key_by_index(*n).unwrap().collect::<Vec<_>>(); |
265 | 2.46M | full_key >= key && full_key.starts_with(&key)814k |
266 | 2.46M | }) |
267 | 1.04M | .map(|n| trie[n].1.as_ref().unwrap().as_ref().to_vec()126k ); |
268 | 1.04M | assert_eq!( |
269 | | actual, |
270 | | expected, |
271 | 0 | "\nkey = {:?}\ntrie = {:?}", |
272 | 0 | key.iter().map(|n| format!("{:x}", n)).collect::<String>(), |
273 | | trie |
274 | | ); |
275 | | } |
276 | | } |
277 | 1 | } |
278 | | |
279 | | #[test] |
280 | 1 | fn unknown_block() { |
281 | 1 | let DatabaseOpen::Empty(empty_db) = open(Config { |
282 | 1 | block_number_bytes: 4, |
283 | 1 | cache_size: 2 * 1024 * 1024, |
284 | 1 | ty: ConfigTy::Memory, |
285 | 1 | }) |
286 | 1 | .unwrap() else { |
287 | 0 | panic!() |
288 | | }; |
289 | | |
290 | 1 | let db = empty_db |
291 | 1 | .initialize( |
292 | 1 | &header::HeaderRef { |
293 | 1 | number: 0, |
294 | 1 | extrinsics_root: &[0; 32], |
295 | 1 | parent_hash: &[0; 32], |
296 | 1 | state_root: &[1; 32], |
297 | 1 | digest: header::DigestRef::empty(), |
298 | 1 | } |
299 | 1 | .scale_encoding_vec(4), |
300 | 1 | iter::empty(), |
301 | 1 | None, |
302 | 1 | ) |
303 | 1 | .unwrap(); |
304 | 1 | |
305 | 1 | assert!(matches!0 ( |
306 | 1 | db.block_storage_get(&[0xff; 32], iter::empty::<iter::Empty<_>>(), [].into_iter()), |
307 | | Err(StorageAccessError::UnknownBlock) |
308 | | )); |
309 | | |
310 | 1 | assert!(matches!0 ( |
311 | 1 | db.block_storage_next_key( |
312 | 1 | &[0xff; 32], |
313 | 1 | iter::empty::<iter::Empty<_>>(), |
314 | 1 | [].into_iter(), |
315 | 1 | [].into_iter(), |
316 | 1 | true |
317 | 1 | ), |
318 | | Err(StorageAccessError::UnknownBlock) |
319 | | )); |
320 | 1 | assert!(matches!0 ( |
321 | 1 | db.block_storage_next_key( |
322 | 1 | &[0xff; 32], |
323 | 1 | iter::empty::<iter::Empty<_>>(), |
324 | 1 | [].into_iter(), |
325 | 1 | [].into_iter(), |
326 | 1 | false |
327 | 1 | ), |
328 | | Err(StorageAccessError::UnknownBlock) |
329 | | )); |
330 | | |
331 | 1 | assert!(matches!0 ( |
332 | 1 | db.block_storage_closest_descendant_merkle_value( |
333 | 1 | &[0xff; 32], |
334 | 1 | iter::empty::<iter::Empty<_>>(), |
335 | 1 | [].into_iter() |
336 | 1 | ), |
337 | | Err(StorageAccessError::UnknownBlock) |
338 | | )); |
339 | 1 | } |
340 | | |
341 | | #[test] |
342 | 1 | fn storage_get_partial() { |
343 | 1 | let DatabaseOpen::Empty(empty_db) = open(Config { |
344 | 1 | block_number_bytes: 4, |
345 | 1 | cache_size: 2 * 1024 * 1024, |
346 | 1 | ty: ConfigTy::Memory, |
347 | 1 | }) |
348 | 1 | .unwrap() else { |
349 | 0 | panic!() |
350 | | }; |
351 | | |
352 | 1 | let db = empty_db |
353 | 1 | .initialize( |
354 | 1 | &header::HeaderRef { |
355 | 1 | number: 0, |
356 | 1 | extrinsics_root: &[0; 32], |
357 | 1 | parent_hash: &[0; 32], |
358 | 1 | state_root: &[1; 32], |
359 | 1 | digest: header::DigestRef::empty(), |
360 | 1 | } |
361 | 1 | .scale_encoding_vec(4), |
362 | 1 | iter::empty(), |
363 | 1 | None, |
364 | 1 | ) |
365 | 1 | .unwrap(); |
366 | 1 | |
367 | 1 | assert!(matches!0 ( |
368 | 1 | db.block_storage_get( |
369 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
370 | 1 | iter::empty::<iter::Empty<_>>(), |
371 | 1 | [1, 1].into_iter(), |
372 | 1 | ), |
373 | | Err(StorageAccessError::IncompleteStorage) |
374 | | )); |
375 | | |
376 | 1 | assert!(matches!0 ( |
377 | 1 | db.block_storage_get( |
378 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
379 | 1 | iter::empty::<iter::Empty<_>>(), |
380 | 1 | [1, 1, 1, 1, 1].into_iter(), |
381 | 1 | ), |
382 | | Err(StorageAccessError::IncompleteStorage) |
383 | | )); |
384 | | |
385 | 1 | assert!(matches!0 ( |
386 | 1 | db.block_storage_get( |
387 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
388 | 1 | iter::empty::<iter::Empty<_>>(), |
389 | 1 | [1, 1, 2].into_iter(), |
390 | 1 | ), |
391 | | Err(StorageAccessError::IncompleteStorage) |
392 | | )); |
393 | | |
394 | 1 | assert!(matches!0 ( |
395 | 1 | db.block_storage_get( |
396 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
397 | 1 | iter::empty::<iter::Empty<_>>(), |
398 | 1 | [1, 1, 1, 2].into_iter(), |
399 | 1 | ), |
400 | | Err(StorageAccessError::IncompleteStorage) |
401 | | )); |
402 | | |
403 | | // The empty key is specifically tested due to SQLite having some weird behaviors mixing |
404 | | // null and empty bytes. |
405 | 1 | assert!(matches!0 ( |
406 | 1 | db.block_storage_get( |
407 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
408 | 1 | iter::empty::<iter::Empty<_>>(), |
409 | 1 | [].into_iter(), |
410 | 1 | ), |
411 | | Err(StorageAccessError::IncompleteStorage) |
412 | | )); |
413 | | |
414 | 1 | db.insert_trie_nodes( |
415 | 1 | [InsertTrieNode { |
416 | 1 | merkle_value: Cow::Borrowed(&[1; 32]), |
417 | 1 | partial_key_nibbles: Cow::Borrowed(&[1, 1]), |
418 | 1 | children_merkle_values: [ |
419 | 1 | None, |
420 | 1 | Some(Cow::Borrowed(&[2; 32])), |
421 | 1 | None, |
422 | 1 | None, |
423 | 1 | None, |
424 | 1 | None, |
425 | 1 | None, |
426 | 1 | None, |
427 | 1 | None, |
428 | 1 | None, |
429 | 1 | None, |
430 | 1 | None, |
431 | 1 | None, |
432 | 1 | None, |
433 | 1 | None, |
434 | 1 | None, |
435 | 1 | ], |
436 | 1 | storage_value: InsertTrieNodeStorageValue::Value { |
437 | 1 | value: Cow::Borrowed(b"hello"), |
438 | 1 | references_merkle_value: false, |
439 | 1 | }, |
440 | 1 | }] |
441 | 1 | .into_iter(), |
442 | 1 | 0, |
443 | 1 | ) |
444 | 1 | .unwrap(); |
445 | 1 | |
446 | 1 | assert_eq!( |
447 | 1 | db.block_storage_get( |
448 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
449 | 1 | iter::empty::<iter::Empty<_>>(), |
450 | 1 | [1, 1].into_iter(), |
451 | 1 | ) |
452 | 1 | .unwrap() |
453 | 1 | .unwrap() |
454 | 1 | .0, |
455 | 1 | b"hello" |
456 | 1 | ); |
457 | | |
458 | 1 | assert!(matches!0 ( |
459 | 1 | db.block_storage_get( |
460 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
461 | 1 | iter::empty::<iter::Empty<_>>(), |
462 | 1 | [1, 1, 1, 1, 1].into_iter(), |
463 | 1 | ), |
464 | | Err(StorageAccessError::IncompleteStorage) |
465 | | )); |
466 | | |
467 | 1 | assert!(db |
468 | 1 | .block_storage_get( |
469 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
470 | 1 | iter::empty::<iter::Empty<_>>(), |
471 | 1 | [1, 1, 2].into_iter(), |
472 | 1 | ) |
473 | 1 | .unwrap() |
474 | 1 | .is_none()); |
475 | | |
476 | 1 | assert!(matches!0 ( |
477 | 1 | db.block_storage_get( |
478 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
479 | 1 | iter::empty::<iter::Empty<_>>(), |
480 | 1 | [1, 1, 1, 2].into_iter(), |
481 | 1 | ), |
482 | | Err(StorageAccessError::IncompleteStorage) |
483 | | )); |
484 | | |
485 | 1 | db.insert_trie_nodes( |
486 | 1 | [InsertTrieNode { |
487 | 1 | merkle_value: Cow::Borrowed(&[2; 32]), |
488 | 1 | partial_key_nibbles: Cow::Borrowed(&[1, 1]), |
489 | 1 | children_merkle_values: [ |
490 | 1 | None, None, None, None, None, None, None, None, None, None, None, None, None, None, |
491 | 1 | None, None, |
492 | 1 | ], |
493 | 1 | storage_value: InsertTrieNodeStorageValue::Value { |
494 | 1 | value: Cow::Borrowed(b"world"), |
495 | 1 | references_merkle_value: false, |
496 | 1 | }, |
497 | 1 | }] |
498 | 1 | .into_iter(), |
499 | 1 | 0, |
500 | 1 | ) |
501 | 1 | .unwrap(); |
502 | 1 | |
503 | 1 | assert_eq!( |
504 | 1 | db.block_storage_get( |
505 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
506 | 1 | iter::empty::<iter::Empty<_>>(), |
507 | 1 | [1, 1].into_iter(), |
508 | 1 | ) |
509 | 1 | .unwrap() |
510 | 1 | .unwrap() |
511 | 1 | .0, |
512 | 1 | b"hello" |
513 | 1 | ); |
514 | | |
515 | 1 | assert_eq!( |
516 | 1 | db.block_storage_get( |
517 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
518 | 1 | iter::empty::<iter::Empty<_>>(), |
519 | 1 | [1, 1, 1, 1, 1].into_iter(), |
520 | 1 | ) |
521 | 1 | .unwrap() |
522 | 1 | .unwrap() |
523 | 1 | .0, |
524 | 1 | b"world" |
525 | 1 | ); |
526 | | |
527 | 1 | assert!(db |
528 | 1 | .block_storage_get( |
529 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
530 | 1 | iter::empty::<iter::Empty<_>>(), |
531 | 1 | [1, 1, 2].into_iter(), |
532 | 1 | ) |
533 | 1 | .unwrap() |
534 | 1 | .is_none()); |
535 | | |
536 | 1 | assert!(db |
537 | 1 | .block_storage_get( |
538 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
539 | 1 | iter::empty::<iter::Empty<_>>(), |
540 | 1 | [1, 1, 1, 2].into_iter(), |
541 | 1 | ) |
542 | 1 | .unwrap() |
543 | 1 | .is_none()); |
544 | | |
545 | | // The empty key is specifically tested due to SQLite having some weird behaviors mixing |
546 | | // null and empty bytes. |
547 | 1 | assert!(db |
548 | 1 | .block_storage_get( |
549 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
550 | 1 | iter::empty::<iter::Empty<_>>(), |
551 | 1 | [].into_iter(), |
552 | 1 | ) |
553 | 1 | .unwrap() |
554 | 1 | .is_none()); |
555 | 1 | } |
556 | | |
557 | | #[test] |
558 | 1 | fn storage_next_key_partial() { |
559 | 1 | let DatabaseOpen::Empty(empty_db) = open(Config { |
560 | 1 | block_number_bytes: 4, |
561 | 1 | cache_size: 2 * 1024 * 1024, |
562 | 1 | ty: ConfigTy::Memory, |
563 | 1 | }) |
564 | 1 | .unwrap() else { |
565 | 0 | panic!() |
566 | | }; |
567 | | |
568 | 1 | let db = empty_db |
569 | 1 | .initialize( |
570 | 1 | &header::HeaderRef { |
571 | 1 | number: 0, |
572 | 1 | extrinsics_root: &[0; 32], |
573 | 1 | parent_hash: &[0; 32], |
574 | 1 | state_root: &[1; 32], |
575 | 1 | digest: header::DigestRef::empty(), |
576 | 1 | } |
577 | 1 | .scale_encoding_vec(4), |
578 | 1 | iter::empty(), |
579 | 1 | None, |
580 | 1 | ) |
581 | 1 | .unwrap(); |
582 | 1 | |
583 | 1 | // The empty key is specifically tested due to SQLite having some weird behaviors mixing |
584 | 1 | // null and empty bytes. |
585 | 1 | assert!(matches!0 ( |
586 | 1 | db.block_storage_next_key( |
587 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
588 | 1 | iter::empty::<iter::Empty<_>>(), |
589 | 1 | [].into_iter(), |
590 | 1 | iter::empty(), |
591 | 1 | true |
592 | 1 | ), |
593 | | Err(StorageAccessError::IncompleteStorage) |
594 | | )); |
595 | | |
596 | 1 | assert!(matches!0 ( |
597 | 1 | db.block_storage_next_key( |
598 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
599 | 1 | iter::empty::<iter::Empty<_>>(), |
600 | 1 | [1, 1].into_iter(), |
601 | 1 | iter::empty(), |
602 | 1 | true |
603 | 1 | ), |
604 | | Err(StorageAccessError::IncompleteStorage) |
605 | | )); |
606 | | |
607 | 1 | db.insert_trie_nodes( |
608 | 1 | [InsertTrieNode { |
609 | 1 | merkle_value: Cow::Borrowed(&[1; 32]), |
610 | 1 | partial_key_nibbles: Cow::Borrowed(&[1, 1]), |
611 | 1 | children_merkle_values: [ |
612 | 1 | None, |
613 | 1 | Some(Cow::Borrowed(&[2; 32])), |
614 | 1 | Some(Cow::Borrowed(&[3; 32])), |
615 | 1 | None, |
616 | 1 | None, |
617 | 1 | None, |
618 | 1 | None, |
619 | 1 | None, |
620 | 1 | None, |
621 | 1 | None, |
622 | 1 | None, |
623 | 1 | None, |
624 | 1 | None, |
625 | 1 | None, |
626 | 1 | None, |
627 | 1 | None, |
628 | 1 | ], |
629 | 1 | storage_value: InsertTrieNodeStorageValue::NoValue, |
630 | 1 | }] |
631 | 1 | .into_iter(), |
632 | 1 | 0, |
633 | 1 | ) |
634 | 1 | .unwrap(); |
635 | 1 | |
636 | 1 | assert_eq!( |
637 | 1 | db.block_storage_next_key( |
638 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
639 | 1 | iter::empty::<iter::Empty<_>>(), |
640 | 1 | [1, 0].into_iter(), |
641 | 1 | iter::empty(), |
642 | 1 | true |
643 | 1 | ) |
644 | 1 | .unwrap() |
645 | 1 | .unwrap(), |
646 | 1 | vec![1, 1] |
647 | 1 | ); |
648 | | |
649 | 1 | assert_eq!( |
650 | 1 | db.block_storage_next_key( |
651 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
652 | 1 | iter::empty::<iter::Empty<_>>(), |
653 | 1 | [1, 1].into_iter(), |
654 | 1 | iter::empty(), |
655 | 1 | true |
656 | 1 | ) |
657 | 1 | .unwrap() |
658 | 1 | .unwrap(), |
659 | 1 | vec![1, 1] |
660 | 1 | ); |
661 | | |
662 | 1 | assert!(matches!0 ( |
663 | 1 | db.block_storage_next_key( |
664 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
665 | 1 | iter::empty::<iter::Empty<_>>(), |
666 | 1 | [1, 1].into_iter(), |
667 | 1 | iter::empty(), |
668 | 1 | false |
669 | 1 | ), |
670 | | Err(StorageAccessError::IncompleteStorage) |
671 | | )); |
672 | | |
673 | 1 | assert!(matches!0 ( |
674 | 1 | db.block_storage_next_key( |
675 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
676 | 1 | iter::empty::<iter::Empty<_>>(), |
677 | 1 | [1, 1, 0].into_iter(), |
678 | 1 | iter::empty(), |
679 | 1 | true |
680 | 1 | ), |
681 | | Err(StorageAccessError::IncompleteStorage) |
682 | | )); |
683 | | |
684 | 1 | assert!(matches!0 ( |
685 | 1 | db.block_storage_next_key( |
686 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
687 | 1 | iter::empty::<iter::Empty<_>>(), |
688 | 1 | [1, 1, 0].into_iter(), |
689 | 1 | [1, 1, 0].into_iter(), |
690 | 1 | true |
691 | 1 | ), |
692 | | Ok(None) |
693 | | )); |
694 | | |
695 | 1 | assert!(matches!0 ( |
696 | 1 | db.block_storage_next_key( |
697 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
698 | 1 | iter::empty::<iter::Empty<_>>(), |
699 | 1 | [1, 1, 2].into_iter(), |
700 | 1 | iter::empty(), |
701 | 1 | true |
702 | 1 | ), |
703 | | Err(StorageAccessError::IncompleteStorage) |
704 | | )); |
705 | | |
706 | 1 | assert!(matches!0 ( |
707 | 1 | db.block_storage_next_key( |
708 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
709 | 1 | iter::empty::<iter::Empty<_>>(), |
710 | 1 | [1, 1, 3].into_iter(), |
711 | 1 | iter::empty(), |
712 | 1 | true |
713 | 1 | ), |
714 | | Ok(None) |
715 | | )); |
716 | | |
717 | 1 | db.insert_trie_nodes( |
718 | 1 | [InsertTrieNode { |
719 | 1 | merkle_value: Cow::Borrowed(&[3; 32]), |
720 | 1 | partial_key_nibbles: Cow::Borrowed(&[2]), |
721 | 1 | children_merkle_values: [ |
722 | 1 | None, None, None, None, None, None, None, None, None, None, None, None, None, None, |
723 | 1 | None, None, |
724 | 1 | ], |
725 | 1 | storage_value: InsertTrieNodeStorageValue::Value { |
726 | 1 | value: Cow::Borrowed(b"hello"), |
727 | 1 | references_merkle_value: false, |
728 | 1 | }, |
729 | 1 | }] |
730 | 1 | .into_iter(), |
731 | 1 | 0, |
732 | 1 | ) |
733 | 1 | .unwrap(); |
734 | 1 | |
735 | 1 | assert_eq!( |
736 | 1 | db.block_storage_next_key( |
737 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
738 | 1 | iter::empty::<iter::Empty<_>>(), |
739 | 1 | [1, 1, 2].into_iter(), |
740 | 1 | iter::empty(), |
741 | 1 | true |
742 | 1 | ) |
743 | 1 | .unwrap() |
744 | 1 | .unwrap(), |
745 | 1 | vec![1, 1, 2, 2] |
746 | 1 | ); |
747 | | |
748 | 1 | assert!(matches!0 ( |
749 | 1 | db.block_storage_next_key( |
750 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
751 | 1 | iter::empty::<iter::Empty<_>>(), |
752 | 1 | [1, 1, 1, 1, 1, 1, 1, 1].into_iter(), |
753 | 1 | iter::empty(), |
754 | 1 | true |
755 | 1 | ), |
756 | | Err(StorageAccessError::IncompleteStorage) |
757 | | )); |
758 | | |
759 | 1 | db.insert_trie_nodes( |
760 | 1 | [InsertTrieNode { |
761 | 1 | merkle_value: Cow::Borrowed(&[2; 32]), |
762 | 1 | partial_key_nibbles: Cow::Borrowed(&[1, 1]), |
763 | 1 | children_merkle_values: [ |
764 | 1 | None, None, None, None, None, None, None, None, None, None, None, None, None, None, |
765 | 1 | None, None, |
766 | 1 | ], |
767 | 1 | storage_value: InsertTrieNodeStorageValue::Value { |
768 | 1 | value: Cow::Borrowed(b"hello"), |
769 | 1 | references_merkle_value: false, |
770 | 1 | }, |
771 | 1 | }] |
772 | 1 | .into_iter(), |
773 | 1 | 0, |
774 | 1 | ) |
775 | 1 | .unwrap(); |
776 | 1 | |
777 | 1 | assert_eq!( |
778 | 1 | db.block_storage_next_key( |
779 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
780 | 1 | iter::empty::<iter::Empty<_>>(), |
781 | 1 | [].into_iter(), |
782 | 1 | iter::empty(), |
783 | 1 | false |
784 | 1 | ) |
785 | 1 | .unwrap() |
786 | 1 | .unwrap(), |
787 | 1 | vec![1, 1, 1, 1, 1] |
788 | 1 | ); |
789 | | |
790 | 1 | assert_eq!( |
791 | 1 | db.block_storage_next_key( |
792 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
793 | 1 | iter::empty::<iter::Empty<_>>(), |
794 | 1 | [1, 1, 1, 1, 1, 1, 1, 1].into_iter(), |
795 | 1 | iter::empty(), |
796 | 1 | true |
797 | 1 | ) |
798 | 1 | .unwrap() |
799 | 1 | .unwrap(), |
800 | 1 | vec![1, 1, 2, 2] |
801 | 1 | ); |
802 | | |
803 | 1 | assert_eq!( |
804 | 1 | db.block_storage_next_key( |
805 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
806 | 1 | iter::empty::<iter::Empty<_>>(), |
807 | 1 | [1, 1, 3].into_iter(), |
808 | 1 | iter::empty(), |
809 | 1 | true |
810 | 1 | ) |
811 | 1 | .unwrap(), |
812 | 1 | None |
813 | 1 | ); |
814 | | |
815 | 1 | assert_eq!( |
816 | 1 | db.block_storage_next_key( |
817 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
818 | 1 | iter::empty::<iter::Empty<_>>(), |
819 | 1 | [3].into_iter(), |
820 | 1 | iter::empty(), |
821 | 1 | true |
822 | 1 | ) |
823 | 1 | .unwrap(), |
824 | 1 | None |
825 | 1 | ); |
826 | 1 | } |
827 | | |
828 | | #[test] |
829 | 1 | fn storage_closest_descendant_merkle_value_partial() { |
830 | 1 | let DatabaseOpen::Empty(empty_db) = open(Config { |
831 | 1 | block_number_bytes: 4, |
832 | 1 | cache_size: 2 * 1024 * 1024, |
833 | 1 | ty: ConfigTy::Memory, |
834 | 1 | }) |
835 | 1 | .unwrap() else { |
836 | 0 | panic!() |
837 | | }; |
838 | | |
839 | 1 | let db = empty_db |
840 | 1 | .initialize( |
841 | 1 | &header::HeaderRef { |
842 | 1 | number: 0, |
843 | 1 | extrinsics_root: &[0; 32], |
844 | 1 | parent_hash: &[0; 32], |
845 | 1 | state_root: &[1; 32], |
846 | 1 | digest: header::DigestRef::empty(), |
847 | 1 | } |
848 | 1 | .scale_encoding_vec(4), |
849 | 1 | iter::empty(), |
850 | 1 | None, |
851 | 1 | ) |
852 | 1 | .unwrap(); |
853 | 1 | |
854 | 1 | assert_eq!( |
855 | 1 | db.block_storage_closest_descendant_merkle_value( |
856 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
857 | 1 | iter::empty::<iter::Empty<_>>(), |
858 | 1 | [].into_iter(), |
859 | 1 | ) |
860 | 1 | .unwrap() |
861 | 1 | .unwrap(), |
862 | 1 | vec![1; 32] |
863 | 1 | ); |
864 | | |
865 | 1 | assert!(matches!0 ( |
866 | 1 | db.block_storage_closest_descendant_merkle_value( |
867 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
868 | 1 | iter::empty::<iter::Empty<_>>(), |
869 | 1 | [1].into_iter(), |
870 | 1 | ), |
871 | | Err(StorageAccessError::IncompleteStorage) |
872 | | )); |
873 | | |
874 | 1 | assert!(matches!0 ( |
875 | 1 | db.block_storage_closest_descendant_merkle_value( |
876 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
877 | 1 | iter::empty::<iter::Empty<_>>(), |
878 | 1 | [1, 1].into_iter(), |
879 | 1 | ), |
880 | | Err(StorageAccessError::IncompleteStorage) |
881 | | )); |
882 | | |
883 | 1 | assert!(matches!0 ( |
884 | 1 | db.block_storage_closest_descendant_merkle_value( |
885 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
886 | 1 | iter::empty::<iter::Empty<_>>(), |
887 | 1 | [1, 2, 3, 4, 5].into_iter(), |
888 | 1 | ), |
889 | | Err(StorageAccessError::IncompleteStorage) |
890 | | )); |
891 | | |
892 | 1 | db.insert_trie_nodes( |
893 | 1 | [InsertTrieNode { |
894 | 1 | merkle_value: Cow::Borrowed(&[1; 32]), |
895 | 1 | partial_key_nibbles: Cow::Borrowed(&[1, 1]), |
896 | 1 | children_merkle_values: [ |
897 | 1 | None, |
898 | 1 | Some(Cow::Borrowed(&[2; 32])), |
899 | 1 | None, |
900 | 1 | None, |
901 | 1 | None, |
902 | 1 | None, |
903 | 1 | None, |
904 | 1 | None, |
905 | 1 | None, |
906 | 1 | None, |
907 | 1 | None, |
908 | 1 | None, |
909 | 1 | None, |
910 | 1 | None, |
911 | 1 | None, |
912 | 1 | None, |
913 | 1 | ], |
914 | 1 | storage_value: InsertTrieNodeStorageValue::Value { |
915 | 1 | value: Cow::Borrowed(b"hello"), |
916 | 1 | references_merkle_value: false, |
917 | 1 | }, |
918 | 1 | }] |
919 | 1 | .into_iter(), |
920 | 1 | 0, |
921 | 1 | ) |
922 | 1 | .unwrap(); |
923 | 1 | |
924 | 1 | assert_eq!( |
925 | 1 | db.block_storage_closest_descendant_merkle_value( |
926 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
927 | 1 | iter::empty::<iter::Empty<_>>(), |
928 | 1 | [1].into_iter(), |
929 | 1 | ) |
930 | 1 | .unwrap() |
931 | 1 | .unwrap(), |
932 | 1 | vec![1; 32] |
933 | 1 | ); |
934 | | |
935 | 1 | assert_eq!( |
936 | 1 | db.block_storage_closest_descendant_merkle_value( |
937 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
938 | 1 | iter::empty::<iter::Empty<_>>(), |
939 | 1 | [1, 1].into_iter(), |
940 | 1 | ) |
941 | 1 | .unwrap() |
942 | 1 | .unwrap(), |
943 | 1 | vec![1; 32] |
944 | 1 | ); |
945 | | |
946 | 1 | assert_eq!( |
947 | 1 | db.block_storage_closest_descendant_merkle_value( |
948 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
949 | 1 | iter::empty::<iter::Empty<_>>(), |
950 | 1 | [1, 1, 1].into_iter(), |
951 | 1 | ) |
952 | 1 | .unwrap() |
953 | 1 | .unwrap(), |
954 | 1 | vec![2; 32] |
955 | 1 | ); |
956 | | |
957 | 1 | assert!(matches!0 ( |
958 | 1 | db.block_storage_closest_descendant_merkle_value( |
959 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
960 | 1 | iter::empty::<iter::Empty<_>>(), |
961 | 1 | [1, 1, 1, 1].into_iter(), |
962 | 1 | ), |
963 | | Err(StorageAccessError::IncompleteStorage) |
964 | | )); |
965 | | |
966 | 1 | assert_eq!( |
967 | 1 | db.block_storage_closest_descendant_merkle_value( |
968 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
969 | 1 | iter::empty::<iter::Empty<_>>(), |
970 | 1 | [1, 1, 2].into_iter(), |
971 | 1 | ) |
972 | 1 | .unwrap(), |
973 | 1 | None |
974 | 1 | ); |
975 | | |
976 | 1 | assert_eq!( |
977 | 1 | db.block_storage_closest_descendant_merkle_value( |
978 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
979 | 1 | iter::empty::<iter::Empty<_>>(), |
980 | 1 | [5].into_iter(), |
981 | 1 | ) |
982 | 1 | .unwrap(), |
983 | 1 | None |
984 | 1 | ); |
985 | | |
986 | 1 | db.insert_trie_nodes( |
987 | 1 | [InsertTrieNode { |
988 | 1 | merkle_value: Cow::Borrowed(&[2; 32]), |
989 | 1 | partial_key_nibbles: Cow::Borrowed(&[1, 1]), |
990 | 1 | children_merkle_values: [ |
991 | 1 | None, None, None, None, None, None, None, None, None, None, None, None, None, None, |
992 | 1 | None, None, |
993 | 1 | ], |
994 | 1 | storage_value: InsertTrieNodeStorageValue::Value { |
995 | 1 | value: Cow::Borrowed(b"world"), |
996 | 1 | references_merkle_value: false, |
997 | 1 | }, |
998 | 1 | }] |
999 | 1 | .into_iter(), |
1000 | 1 | 0, |
1001 | 1 | ) |
1002 | 1 | .unwrap(); |
1003 | 1 | |
1004 | 1 | assert_eq!( |
1005 | 1 | db.block_storage_closest_descendant_merkle_value( |
1006 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
1007 | 1 | iter::empty::<iter::Empty<_>>(), |
1008 | 1 | [1, 1].into_iter(), |
1009 | 1 | ) |
1010 | 1 | .unwrap() |
1011 | 1 | .unwrap(), |
1012 | 1 | vec![1; 32] |
1013 | 1 | ); |
1014 | | |
1015 | 1 | assert_eq!( |
1016 | 1 | db.block_storage_closest_descendant_merkle_value( |
1017 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
1018 | 1 | iter::empty::<iter::Empty<_>>(), |
1019 | 1 | [1, 1, 1].into_iter(), |
1020 | 1 | ) |
1021 | 1 | .unwrap() |
1022 | 1 | .unwrap(), |
1023 | 1 | vec![2; 32] |
1024 | 1 | ); |
1025 | | |
1026 | 1 | assert_eq!( |
1027 | 1 | db.block_storage_closest_descendant_merkle_value( |
1028 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
1029 | 1 | iter::empty::<iter::Empty<_>>(), |
1030 | 1 | [1, 1, 1, 1].into_iter(), |
1031 | 1 | ) |
1032 | 1 | .unwrap() |
1033 | 1 | .unwrap(), |
1034 | 1 | vec![2; 32] |
1035 | 1 | ); |
1036 | | |
1037 | 1 | assert_eq!( |
1038 | 1 | db.block_storage_closest_descendant_merkle_value( |
1039 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
1040 | 1 | iter::empty::<iter::Empty<_>>(), |
1041 | 1 | [1, 1, 2].into_iter(), |
1042 | 1 | ) |
1043 | 1 | .unwrap(), |
1044 | 1 | None |
1045 | 1 | ); |
1046 | | |
1047 | 1 | assert_eq!( |
1048 | 1 | db.block_storage_closest_descendant_merkle_value( |
1049 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
1050 | 1 | iter::empty::<iter::Empty<_>>(), |
1051 | 1 | [1, 1, 1, 2].into_iter(), |
1052 | 1 | ) |
1053 | 1 | .unwrap(), |
1054 | 1 | None |
1055 | 1 | ); |
1056 | | |
1057 | 1 | assert_eq!( |
1058 | 1 | db.block_storage_closest_descendant_merkle_value( |
1059 | 1 | &db.block_hash_by_number(0).unwrap().next().unwrap(), |
1060 | 1 | iter::empty::<iter::Empty<_>>(), |
1061 | 1 | [1, 1, 1, 1, 1, 1, 1, 1].into_iter(), |
1062 | 1 | ) |
1063 | 1 | .unwrap(), |
1064 | 1 | None |
1065 | 1 | ); |
1066 | 1 | } |