1#[cfg(all(test, feature = "arbitrary"))]
104mod conformance;
105mod storage;
106
107use std::num::{NonZeroU64, NonZeroUsize};
108pub use storage::Ordinal;
109use thiserror::Error;
110
111#[derive(Debug, Error)]
113pub enum Error {
114 #[error("runtime error: {0}")]
115 Runtime(#[from] commonware_runtime::Error),
116 #[error("invalid blob name: {0}")]
117 InvalidBlobName(String),
118 #[error("invalid record: {0}")]
119 InvalidRecord(u64),
120 #[error("missing record at {0}")]
121 MissingRecord(u64),
122}
123
124#[derive(Clone)]
126pub struct Config {
127 pub partition: String,
129
130 pub items_per_blob: NonZeroU64,
132
133 pub write_buffer: NonZeroUsize,
135
136 pub replay_buffer: NonZeroUsize,
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143 use crate::utils::bits_for_indices;
144 use commonware_codec::{Buf, FixedSize, Read, ReadExt, Write};
145 use commonware_cryptography::Crc32;
146 use commonware_formatting::hex;
147 use commonware_macros::{test_group, test_traced};
148 use commonware_runtime::{
149 Blob, BufMut, Metrics as _, ReadOptions, Runner, Storage, Supervisor as _, WriteOptions,
150 deterministic,
151 mocks::{
152 DelayedSyncContext, PendingSyncs, RecordingContext, SyncFaultContext,
153 drive_pending_syncs,
154 },
155 };
156 use commonware_utils::{NZU64, NZUsize, bitmap::BitMap, sequence::FixedBytes};
157 use rand::Rng;
158 use std::{collections::BTreeMap, num::NonZeroUsize};
159
160 const DEFAULT_ITEMS_PER_BLOB: u64 = 1000;
161 const DEFAULT_WRITE_BUFFER: usize = 4096;
162 const DEFAULT_REPLAY_BUFFER: usize = 1024 * 1024;
163
164 #[test_traced]
165 fn test_put_get() {
166 let executor = deterministic::Runner::default();
168 executor.start(|context| async move {
169 let cfg = Config {
171 partition: "test-ordinal".into(),
172 items_per_blob: NZU64!(DEFAULT_ITEMS_PER_BLOB),
173 write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
174 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
175 };
176 let mut store =
177 Ordinal::<_, FixedBytes<32>>::init(context.child("storage"), cfg.clone(), None)
178 .await
179 .expect("Failed to initialize store");
180
181 let value = FixedBytes::new([42u8; 32]);
182
183 assert!(!store.has(0));
185
186 store = store
188 .put(0, value.clone())
189 .await
190 .expect("Failed to put data");
191
192 assert!(store.has(0));
194
195 let retrieved = store
197 .get(0)
198 .await
199 .expect("Failed to get data")
200 .expect("Data not found");
201 assert_eq!(retrieved, value);
202
203 store = store.sync().await.expect("Failed to sync data");
205
206 let buffer = context.encode();
208 assert!(buffer.contains("gets_total 1"), "{}", buffer);
209 assert!(buffer.contains("puts_total 1"), "{}", buffer);
210 assert!(buffer.contains("has_total 2"), "{}", buffer);
211 assert!(buffer.contains("syncs_total 1"), "{}", buffer);
212 assert!(buffer.contains("pruned_total 0"), "{}", buffer);
213
214 let retrieved = store
216 .get(0)
217 .await
218 .expect("Failed to get data")
219 .expect("Data not found");
220 assert_eq!(retrieved, value);
221 });
222 }
223
224 #[test_traced]
225 fn test_sync_does_not_report_success_while_flush_fails() {
226 let executor = deterministic::Runner::default();
227 executor.start(|context| async move {
228 let cfg = Config {
229 partition: "test-ordinal".into(),
230 items_per_blob: NZU64!(DEFAULT_ITEMS_PER_BLOB),
231 write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
232 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
233 };
234 let mut store =
235 Ordinal::<_, FixedBytes<32>>::init(context.child("storage"), cfg.clone(), None)
236 .await
237 .expect("Failed to initialize store");
238
239 store = store
240 .put(0, FixedBytes::new([42u8; 32]))
241 .await
242 .expect("Failed to put data");
243
244 let section = 0u64.to_be_bytes();
246 context
247 .remove(&cfg.partition, Some(§ion))
248 .await
249 .expect("Failed to remove blob");
250
251 assert!(store.sync().await.is_err(), "sync unexpectedly succeeded");
253 });
254 }
255
256 #[test_traced]
257 fn test_multiple_indices() {
258 let executor = deterministic::Runner::default();
260 executor.start(|context| async move {
261 let cfg = Config {
263 partition: "test-ordinal".into(),
264 items_per_blob: NZU64!(DEFAULT_ITEMS_PER_BLOB),
265 write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
266 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
267 };
268 let mut store =
269 Ordinal::<_, FixedBytes<32>>::init(context.child("storage"), cfg.clone(), None)
270 .await
271 .expect("Failed to initialize store");
272
273 let indices = vec![
275 (0u64, FixedBytes::new([0u8; 32])),
276 (5u64, FixedBytes::new([5u8; 32])),
277 (10u64, FixedBytes::new([10u8; 32])),
278 (100u64, FixedBytes::new([100u8; 32])),
279 (1000u64, FixedBytes::new([200u8; 32])), ];
281
282 for (index, value) in &indices {
283 store = store
284 .put(*index, value.clone())
285 .await
286 .expect("Failed to put data");
287 }
288
289 store = store.sync().await.expect("Failed to sync");
291
292 for (index, value) in &indices {
294 let retrieved = store
295 .get(*index)
296 .await
297 .expect("Failed to get data")
298 .expect("Data not found");
299 assert_eq!(&retrieved, value);
300 }
301 });
302 }
303
304 #[test_traced]
305 fn test_sparse_indices() {
306 let executor = deterministic::Runner::default();
308 executor.start(|context| async move {
309 let cfg = Config {
311 partition: "test-ordinal".into(),
312 items_per_blob: NZU64!(100), write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
314 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
315 };
316 let mut store =
317 Ordinal::<_, FixedBytes<32>>::init(context.child("storage"), cfg.clone(), None)
318 .await
319 .expect("Failed to initialize store");
320
321 let indices = vec![
323 (0u64, FixedBytes::new([0u8; 32])),
324 (99u64, FixedBytes::new([99u8; 32])), (100u64, FixedBytes::new([100u8; 32])), (500u64, FixedBytes::new([200u8; 32])), ];
328
329 for (index, value) in &indices {
330 store = store
331 .put(*index, value.clone())
332 .await
333 .expect("Failed to put data");
334 }
335
336 assert!(!store.has(1));
338 assert!(!store.has(50));
339 assert!(!store.has(101));
340 assert!(!store.has(499));
341
342 store = store.sync().await.expect("Failed to sync");
344
345 for (index, value) in &indices {
346 let retrieved = store
347 .get(*index)
348 .await
349 .expect("Failed to get data")
350 .expect("Data not found");
351 assert_eq!(&retrieved, value);
352 }
353 });
354 }
355
356 #[test_traced]
357 fn test_next_gap() {
358 let executor = deterministic::Runner::default();
360 executor.start(|context| async move {
361 let cfg = Config {
363 partition: "test-ordinal".into(),
364 items_per_blob: NZU64!(DEFAULT_ITEMS_PER_BLOB),
365 write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
366 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
367 };
368 let mut store =
369 Ordinal::<_, FixedBytes<32>>::init(context.child("storage"), cfg.clone(), None)
370 .await
371 .expect("Failed to initialize store");
372
373 store = store.put(1, FixedBytes::new([1u8; 32])).await.unwrap();
375 store = store.put(10, FixedBytes::new([10u8; 32])).await.unwrap();
376 store = store.put(11, FixedBytes::new([11u8; 32])).await.unwrap();
377 store = store.put(14, FixedBytes::new([14u8; 32])).await.unwrap();
378
379 let (current_end, start_next) = store.next_gap(0);
381 assert!(current_end.is_none());
382 assert_eq!(start_next, Some(1));
383
384 let (current_end, start_next) = store.next_gap(1);
385 assert_eq!(current_end, Some(1));
386 assert_eq!(start_next, Some(10));
387
388 let (current_end, start_next) = store.next_gap(10);
389 assert_eq!(current_end, Some(11));
390 assert_eq!(start_next, Some(14));
391
392 let (current_end, start_next) = store.next_gap(11);
393 assert_eq!(current_end, Some(11));
394 assert_eq!(start_next, Some(14));
395
396 let (current_end, start_next) = store.next_gap(12);
397 assert!(current_end.is_none());
398 assert_eq!(start_next, Some(14));
399
400 let (current_end, start_next) = store.next_gap(14);
401 assert_eq!(current_end, Some(14));
402 assert!(start_next.is_none());
403 });
404 }
405
406 #[test_traced]
407 fn test_missing_items() {
408 let executor = deterministic::Runner::default();
410 executor.start(|context| async move {
411 let cfg = Config {
413 partition: "test-ordinal".into(),
414 items_per_blob: NZU64!(DEFAULT_ITEMS_PER_BLOB),
415 write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
416 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
417 };
418 let mut store =
419 Ordinal::<_, FixedBytes<32>>::init(context.child("storage"), cfg.clone(), None)
420 .await
421 .expect("Failed to initialize store");
422
423 assert_eq!(store.missing_items(0, 5), Vec::<u64>::new());
425 assert_eq!(store.missing_items(100, 10), Vec::<u64>::new());
426
427 store = store.put(1, FixedBytes::new([1u8; 32])).await.unwrap();
429 store = store.put(2, FixedBytes::new([2u8; 32])).await.unwrap();
430 store = store.put(5, FixedBytes::new([5u8; 32])).await.unwrap();
431 store = store.put(6, FixedBytes::new([6u8; 32])).await.unwrap();
432 store = store.put(10, FixedBytes::new([10u8; 32])).await.unwrap();
433
434 assert_eq!(store.missing_items(0, 5), vec![0, 3, 4, 7, 8]);
436 assert_eq!(store.missing_items(0, 6), vec![0, 3, 4, 7, 8, 9]);
437 assert_eq!(store.missing_items(0, 7), vec![0, 3, 4, 7, 8, 9]);
438
439 assert_eq!(store.missing_items(3, 3), vec![3, 4, 7]);
441 assert_eq!(store.missing_items(4, 2), vec![4, 7]);
442
443 assert_eq!(store.missing_items(1, 3), vec![3, 4, 7]);
445 assert_eq!(store.missing_items(2, 4), vec![3, 4, 7, 8]);
446 assert_eq!(store.missing_items(5, 2), vec![7, 8]);
447
448 assert_eq!(store.missing_items(11, 5), Vec::<u64>::new());
450 assert_eq!(store.missing_items(100, 10), Vec::<u64>::new());
451
452 store = store.put(1000, FixedBytes::new([100u8; 32])).await.unwrap();
454
455 let items = store.missing_items(11, 10);
457 assert_eq!(items, vec![11, 12, 13, 14, 15, 16, 17, 18, 19, 20]);
458
459 let items = store.missing_items(990, 15);
461 assert_eq!(
462 items,
463 vec![990, 991, 992, 993, 994, 995, 996, 997, 998, 999]
464 );
465
466 store = store.sync().await.unwrap();
468 assert_eq!(store.missing_items(0, 5), vec![0, 3, 4, 7, 8]);
469 assert_eq!(store.missing_items(3, 3), vec![3, 4, 7]);
470
471 store = store.put(9999, FixedBytes::new([99u8; 32])).await.unwrap();
473 store = store
474 .put(10001, FixedBytes::new([101u8; 32]))
475 .await
476 .unwrap();
477
478 let items = store.missing_items(9998, 5);
480 assert_eq!(items, vec![9998, 10000]);
481 });
482 }
483
484 #[test_traced]
485 fn test_restart() {
486 let executor = deterministic::Runner::default();
488 executor.start(|context| async move {
489 let cfg = Config {
490 partition: "test-ordinal".into(),
491 items_per_blob: NZU64!(DEFAULT_ITEMS_PER_BLOB),
492 write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
493 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
494 };
495
496 {
498 let mut store =
499 Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
500 .await
501 .expect("Failed to initialize store");
502
503 let values = vec![
504 (0u64, FixedBytes::new([0u8; 32])),
505 (100u64, FixedBytes::new([100u8; 32])),
506 (1000u64, FixedBytes::new([200u8; 32])),
507 ];
508
509 for (index, value) in &values {
510 store = store
511 .put(*index, value.clone())
512 .await
513 .expect("Failed to put data");
514 }
515
516 store.sync().await.expect("Failed to sync store");
517 }
518
519 {
521 let mut bits0 = BitMap::zeroes(DEFAULT_ITEMS_PER_BLOB);
522 bits0.set(0, true);
523 bits0.set(100, true);
524 let mut bits1 = BitMap::zeroes(DEFAULT_ITEMS_PER_BLOB);
525 bits1.set(0, true);
526 let bits0 = Some(bits0);
527 let bits1 = Some(bits1);
528 let mut bits = BTreeMap::new();
529 bits.insert(0, &bits0);
530 bits.insert(1, &bits1);
531 let store = Ordinal::<_, FixedBytes<32>>::init(
532 context.child("second"),
533 cfg.clone(),
534 Some(bits),
535 )
536 .await
537 .expect("Failed to initialize store");
538
539 let values = vec![
540 (0u64, FixedBytes::new([0u8; 32])),
541 (100u64, FixedBytes::new([100u8; 32])),
542 (1000u64, FixedBytes::new([200u8; 32])),
543 ];
544
545 for (index, value) in &values {
546 let retrieved = store
547 .get(*index)
548 .await
549 .expect("Failed to get data")
550 .expect("Data not found");
551 assert_eq!(&retrieved, value);
552 }
553
554 let (current_end, start_next) = store.next_gap(0);
556 assert_eq!(current_end, Some(0));
557 assert_eq!(start_next, Some(100));
558 }
559 });
560 }
561
562 #[test_traced]
563 fn test_invalid_record() {
564 let executor = deterministic::Runner::default();
566 executor.start(|context| async move {
567 let cfg = Config {
568 partition: "test-ordinal".into(),
569 items_per_blob: NZU64!(DEFAULT_ITEMS_PER_BLOB),
570 write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
571 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
572 };
573
574 {
576 let mut store =
577 Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
578 .await
579 .expect("Failed to initialize store");
580
581 store = store
582 .put(0, FixedBytes::new([42u8; 32]))
583 .await
584 .expect("Failed to put data");
585 store.sync().await.expect("Failed to sync store");
586 }
587
588 {
590 let (blob, _) = context
591 .open("test-ordinal", &0u64.to_be_bytes())
592 .await
593 .unwrap();
594 blob.write_at(32, vec![0xFF], WriteOptions::SYNC)
596 .await
597 .unwrap();
598 }
599
600 {
602 let store =
603 Ordinal::<_, FixedBytes<32>>::init(context.child("second"), cfg.clone(), None)
604 .await
605 .expect("Failed to initialize store");
606
607 let result = store.get(0).await.unwrap();
608 assert!(result.is_none());
609
610 assert!(!store.has(0));
611 }
612 });
613 }
614
615 #[test_traced]
616 fn test_get_nonexistent() {
617 let executor = deterministic::Runner::default();
619 executor.start(|context| async move {
620 let cfg = Config {
622 partition: "test-ordinal".into(),
623 items_per_blob: NZU64!(DEFAULT_ITEMS_PER_BLOB),
624 write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
625 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
626 };
627 let store =
628 Ordinal::<_, FixedBytes<32>>::init(context.child("storage"), cfg.clone(), None)
629 .await
630 .expect("Failed to initialize store");
631
632 let retrieved = store.get(999).await.expect("Failed to get data");
634 assert!(retrieved.is_none());
635
636 assert!(!store.has(999));
638 });
639 }
640
641 #[test_traced]
642 fn test_destroy() {
643 let executor = deterministic::Runner::default();
645 executor.start(|context| async move {
646 let cfg = Config {
647 partition: "test-ordinal".into(),
648 items_per_blob: NZU64!(DEFAULT_ITEMS_PER_BLOB),
649 write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
650 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
651 };
652
653 {
655 let mut store =
656 Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
657 .await
658 .expect("Failed to initialize store");
659
660 store = store
661 .put(0, FixedBytes::new([0u8; 32]))
662 .await
663 .expect("Failed to put data");
664 store = store
665 .put(1000, FixedBytes::new([100u8; 32]))
666 .await
667 .expect("Failed to put data");
668
669 store.destroy().await.expect("Failed to destroy store");
671 }
672
673 {
675 let store =
676 Ordinal::<_, FixedBytes<32>>::init(context.child("second"), cfg.clone(), None)
677 .await
678 .expect("Failed to initialize store");
679
680 assert!(store.get(0).await.unwrap().is_none());
682 assert!(store.get(1000).await.unwrap().is_none());
683 assert!(!store.has(0));
684 assert!(!store.has(1000));
685 }
686 });
687 }
688
689 #[test_traced]
690 fn test_partial_record_write() {
691 let executor = deterministic::Runner::default();
693 executor.start(|context| async move {
694 let cfg = Config {
695 partition: "test-ordinal".into(),
696 items_per_blob: NZU64!(DEFAULT_ITEMS_PER_BLOB),
697 write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
698 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
699 };
700
701 {
703 let mut store =
704 Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
705 .await
706 .expect("Failed to initialize store");
707
708 store = store
709 .put(0, FixedBytes::new([42u8; 32]))
710 .await
711 .expect("Failed to put data");
712 store = store
713 .put(1, FixedBytes::new([43u8; 32]))
714 .await
715 .expect("Failed to put data");
716 store.sync().await.expect("Failed to sync store");
717 }
718
719 {
721 let (blob, _) = context
722 .open("test-ordinal", &0u64.to_be_bytes())
723 .await
724 .unwrap();
725 blob.write_at(36, vec![0xFF; 32], WriteOptions::SYNC)
727 .await
728 .unwrap();
729 }
730
731 {
733 let store =
734 Ordinal::<_, FixedBytes<32>>::init(context.child("second"), cfg.clone(), None)
735 .await
736 .expect("Failed to initialize store");
737
738 assert!(!store.has(0));
739 assert!(!store.has(1));
740
741 let store = store.put(1, FixedBytes::new([44u8; 32])).await.unwrap();
743 assert_eq!(
744 store.get(1).await.unwrap().unwrap(),
745 FixedBytes::new([44u8; 32])
746 );
747 }
748 });
749 }
750
751 #[test_traced]
752 fn test_corrupted_value() {
753 let executor = deterministic::Runner::default();
755 executor.start(|context| async move {
756 let cfg = Config {
757 partition: "test-ordinal".into(),
758 items_per_blob: NZU64!(DEFAULT_ITEMS_PER_BLOB),
759 write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
760 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
761 };
762
763 {
765 let mut store =
766 Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
767 .await
768 .expect("Failed to initialize store");
769
770 store = store
771 .put(0, FixedBytes::new([42u8; 32]))
772 .await
773 .expect("Failed to put data");
774 store = store
775 .put(1, FixedBytes::new([43u8; 32]))
776 .await
777 .expect("Failed to put data");
778 store.sync().await.expect("Failed to sync store");
779 }
780
781 {
783 let (blob, _) = context
784 .open("test-ordinal", &0u64.to_be_bytes())
785 .await
786 .unwrap();
787 blob.write_at(10, hex!("0xFFFFFFFF").to_vec(), WriteOptions::SYNC)
789 .await
790 .unwrap();
791 }
792
793 {
795 let store =
796 Ordinal::<_, FixedBytes<32>>::init(context.child("second"), cfg.clone(), None)
797 .await
798 .expect("Failed to initialize store");
799
800 assert!(!store.has(0));
801 assert!(!store.has(1));
802 }
803 });
804 }
805
806 #[test_traced]
807 fn test_crc_corruptions() {
808 let executor = deterministic::Runner::default();
810 executor.start(|context| async move {
811 let cfg = Config {
812 partition: "test-ordinal".into(),
813 items_per_blob: NZU64!(10), write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
815 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
816 };
817
818 {
820 let mut store =
821 Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
822 .await
823 .expect("Failed to initialize store");
824
825 store = store.put(0, FixedBytes::new([0u8; 32])).await.unwrap();
827 store = store.put(5, FixedBytes::new([5u8; 32])).await.unwrap();
828 store = store.put(10, FixedBytes::new([10u8; 32])).await.unwrap();
829 store = store.put(15, FixedBytes::new([15u8; 32])).await.unwrap();
830 store.sync().await.expect("Failed to sync store");
831 }
832
833 {
835 let (blob, _) = context
837 .open("test-ordinal", &0u64.to_be_bytes())
838 .await
839 .unwrap();
840 blob.write_at(32, vec![0xFF], WriteOptions::SYNC)
841 .await
842 .unwrap(); let (blob, _) = context
846 .open("test-ordinal", &1u64.to_be_bytes())
847 .await
848 .unwrap();
849 blob.write_at(5, vec![0xFF; 4], WriteOptions::SYNC)
850 .await
851 .unwrap(); }
853
854 {
856 let store =
857 Ordinal::<_, FixedBytes<32>>::init(context.child("second"), cfg.clone(), None)
858 .await
859 .expect("Failed to initialize store");
860
861 assert!(!store.has(0));
862 assert!(!store.has(5));
863 assert!(!store.has(10));
864 assert!(!store.has(15));
865 }
866 });
867 }
868
869 #[test_traced]
870 fn test_extra_bytes_in_blob() {
871 let executor = deterministic::Runner::default();
873 executor.start(|context| async move {
874 let cfg = Config {
875 partition: "test-ordinal".into(),
876 items_per_blob: NZU64!(DEFAULT_ITEMS_PER_BLOB),
877 write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
878 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
879 };
880
881 {
883 let mut store =
884 Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
885 .await
886 .expect("Failed to initialize store");
887
888 store = store
889 .put(0, FixedBytes::new([42u8; 32]))
890 .await
891 .expect("Failed to put data");
892 store = store
893 .put(1, FixedBytes::new([43u8; 32]))
894 .await
895 .expect("Failed to put data");
896 store.sync().await.expect("Failed to sync store");
897 }
898
899 {
901 let (blob, size) = context
902 .open("test-ordinal", &0u64.to_be_bytes())
903 .await
904 .unwrap();
905 let mut garbage = vec![0xFF; 32]; let invalid_crc = 0xDEADBEEFu32;
909 garbage.extend_from_slice(&invalid_crc.to_be_bytes());
910 assert_eq!(garbage.len(), 36); blob.write_at(size, garbage, WriteOptions::SYNC)
912 .await
913 .unwrap();
914 }
915
916 {
918 let store =
919 Ordinal::<_, FixedBytes<32>>::init(context.child("second"), cfg.clone(), None)
920 .await
921 .expect("Failed to initialize store");
922
923 assert!(!store.has(0));
924 assert!(!store.has(1));
925
926 let store = store.put(2, FixedBytes::new([44u8; 32])).await.unwrap();
928 assert_eq!(
929 store.get(2).await.unwrap().unwrap(),
930 FixedBytes::new([44u8; 32])
931 );
932 }
933 });
934 }
935
936 #[test_traced]
937 fn test_zero_filled_records() {
938 let executor = deterministic::Runner::default();
940 executor.start(|context| async move {
941 let cfg = Config {
942 partition: "test-ordinal".into(),
943 items_per_blob: NZU64!(DEFAULT_ITEMS_PER_BLOB),
944 write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
945 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
946 };
947
948 {
950 let (blob, _) = context
951 .open("test-ordinal", &0u64.to_be_bytes())
952 .await
953 .unwrap();
954
955 let zeros = vec![0u8; 36 * 5]; blob.write_at(0, zeros, WriteOptions::SYNC).await.unwrap();
958
959 let mut valid_record = vec![44u8; 32];
961 let crc = Crc32::checksum(&valid_record);
962 valid_record.extend_from_slice(&crc.to_be_bytes());
963 blob.write_at(36 * 5, valid_record, WriteOptions::SYNC)
964 .await
965 .unwrap();
966 }
967
968 {
970 let mut section = BitMap::zeroes(DEFAULT_ITEMS_PER_BLOB);
971 section.set(5, true);
972 let section = Some(section);
973 let mut bits = BTreeMap::new();
974 bits.insert(0, §ion);
975 let store = Ordinal::<_, FixedBytes<32>>::init(
976 context.child("storage"),
977 cfg.clone(),
978 Some(bits),
979 )
980 .await
981 .expect("Failed to initialize store");
982
983 for i in 0..5 {
985 assert!(!store.has(i));
986 }
987
988 assert!(store.has(5));
990 assert_eq!(
991 store.get(5).await.unwrap().unwrap(),
992 FixedBytes::new([44u8; 32])
993 );
994 }
995 });
996 }
997
998 fn test_operations_and_restart(num_values: usize) -> String {
999 let executor = deterministic::Runner::default();
1001 executor.start(|mut context| async move {
1002 let cfg = Config {
1003 partition: "test-ordinal".into(),
1004 items_per_blob: NZU64!(100), write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1006 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1007 };
1008
1009 let mut store =
1011 Ordinal::<_, FixedBytes<128>>::init(context.child("first"), cfg.clone(), None)
1012 .await
1013 .expect("Failed to initialize store");
1014
1015 let mut values = Vec::new();
1017 let mut rng_index = 0u64;
1018
1019 for _ in 0..num_values {
1020 let mut index_bytes = [0u8; 8];
1022 context.fill_bytes(&mut index_bytes);
1023 let index_offset = u64::from_be_bytes(index_bytes) % 1000;
1024 let index = rng_index + index_offset;
1025 rng_index = index + 1;
1026
1027 let mut value = [0u8; 128];
1029 context.fill_bytes(&mut value);
1030 let value = FixedBytes::<128>::new(value);
1031
1032 store = store
1033 .put(index, value.clone())
1034 .await
1035 .expect("Failed to put data");
1036 values.push((index, value));
1037 }
1038
1039 store = store.sync().await.expect("Failed to sync");
1041
1042 for (index, value) in &values {
1044 let retrieved = store
1045 .get(*index)
1046 .await
1047 .expect("Failed to get data")
1048 .expect("Data not found");
1049 assert_eq!(&retrieved, value);
1050 }
1051
1052 for i in 0..10 {
1054 let _ = store.next_gap(i * 100);
1055 }
1056
1057 store.sync().await.expect("Failed to sync store");
1059
1060 let owned_bits = bits_for_indices(NZU64!(100), values.iter().map(|(index, _)| *index));
1062 let bits = owned_bits
1063 .iter()
1064 .map(|(section, bitmap)| (*section, bitmap))
1065 .collect();
1066 let mut store =
1067 Ordinal::<_, FixedBytes<128>>::init(context.child("second"), cfg, Some(bits))
1068 .await
1069 .expect("Failed to initialize store");
1070
1071 for (index, value) in &values {
1073 let retrieved = store
1074 .get(*index)
1075 .await
1076 .expect("Failed to get data")
1077 .expect("Data not found");
1078 assert_eq!(&retrieved, value);
1079 }
1080
1081 for _ in 0..10 {
1083 let mut index_bytes = [0u8; 8];
1084 context.fill_bytes(&mut index_bytes);
1085 let index = u64::from_be_bytes(index_bytes) % 10000;
1086
1087 let mut value = [0u8; 128];
1088 context.fill_bytes(&mut value);
1089 let value = FixedBytes::<128>::new(value);
1090
1091 store = store.put(index, value).await.expect("Failed to put data");
1092 }
1093
1094 store.sync().await.expect("Failed to sync");
1096
1097 context.auditor().state()
1099 })
1100 }
1101
1102 #[test_group("slow")]
1103 #[test_traced]
1104 fn test_determinism() {
1105 let state1 = test_operations_and_restart(100);
1106 let state2 = test_operations_and_restart(100);
1107 assert_eq!(state1, state2);
1108 }
1109
1110 #[test_traced]
1111 fn test_prune_basic() {
1112 let executor = deterministic::Runner::default();
1114 executor.start(|context| async move {
1115 let cfg = Config {
1116 partition: "test-ordinal".into(),
1117 items_per_blob: NZU64!(100), write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1119 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1120 };
1121
1122 let mut store =
1123 Ordinal::<_, FixedBytes<32>>::init(context.child("storage"), cfg.clone(), None)
1124 .await
1125 .expect("Failed to initialize store");
1126
1127 let values = vec![
1129 (0u64, FixedBytes::new([0u8; 32])), (50u64, FixedBytes::new([50u8; 32])), (100u64, FixedBytes::new([100u8; 32])), (150u64, FixedBytes::new([150u8; 32])), (200u64, FixedBytes::new([200u8; 32])), (300u64, FixedBytes::new([44u8; 32])), ];
1136
1137 for (index, value) in &values {
1138 store = store
1139 .put(*index, value.clone())
1140 .await
1141 .expect("Failed to put data");
1142 }
1143 store = store.sync().await.unwrap();
1144
1145 for (index, value) in &values {
1147 assert_eq!(store.get(*index).await.unwrap().unwrap(), *value);
1148 }
1149
1150 store = store.prune(150).await.unwrap();
1152 let buffer = context.encode();
1153 assert!(buffer.contains("pruned_total 1"));
1154
1155 assert!(!store.has(0));
1157 assert!(!store.has(50));
1158 assert!(store.get(0).await.unwrap().is_none());
1159 assert!(store.get(50).await.unwrap().is_none());
1160
1161 assert!(store.has(100));
1163 assert!(store.has(150));
1164 assert!(store.has(200));
1165 assert!(store.has(300));
1166 assert_eq!(store.get(100).await.unwrap().unwrap(), values[2].1);
1167 assert_eq!(store.get(150).await.unwrap().unwrap(), values[3].1);
1168 assert_eq!(store.get(200).await.unwrap().unwrap(), values[4].1);
1169 assert_eq!(store.get(300).await.unwrap().unwrap(), values[5].1);
1170
1171 store = store.prune(250).await.unwrap();
1173 let buffer = context.encode();
1174 assert!(buffer.contains("pruned_total 2"));
1175
1176 assert!(!store.has(100));
1178 assert!(!store.has(150));
1179 assert!(store.get(100).await.unwrap().is_none());
1180 assert!(store.get(150).await.unwrap().is_none());
1181
1182 assert!(store.has(200));
1184 assert!(store.has(300));
1185 assert_eq!(store.get(200).await.unwrap().unwrap(), values[4].1);
1186 assert_eq!(store.get(300).await.unwrap().unwrap(), values[5].1);
1187 });
1188 }
1189
1190 #[test_traced]
1191 fn test_prune_with_gaps() {
1192 let executor = deterministic::Runner::default();
1194 executor.start(|context| async move {
1195 let cfg = Config {
1196 partition: "test-ordinal".into(),
1197 items_per_blob: NZU64!(100),
1198 write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1199 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1200 };
1201
1202 let mut store =
1203 Ordinal::<_, FixedBytes<32>>::init(context.child("storage"), cfg.clone(), None)
1204 .await
1205 .expect("Failed to initialize store");
1206
1207 store = store.put(5, FixedBytes::new([5u8; 32])).await.unwrap();
1209 store = store.put(105, FixedBytes::new([105u8; 32])).await.unwrap();
1210 store = store.put(305, FixedBytes::new([49u8; 32])).await.unwrap();
1211 store = store.sync().await.unwrap();
1212
1213 let (current_end, next_start) = store.next_gap(0);
1215 assert!(current_end.is_none());
1216 assert_eq!(next_start, Some(5));
1217
1218 let (current_end, next_start) = store.next_gap(5);
1219 assert_eq!(current_end, Some(5));
1220 assert_eq!(next_start, Some(105));
1221
1222 store = store.prune(150).await.unwrap();
1224
1225 assert!(!store.has(5));
1227 assert!(store.get(5).await.unwrap().is_none());
1228
1229 assert!(store.has(105));
1231 assert!(store.has(305));
1232
1233 let (current_end, next_start) = store.next_gap(0);
1234 assert!(current_end.is_none());
1235 assert_eq!(next_start, Some(105));
1236
1237 let (current_end, next_start) = store.next_gap(105);
1238 assert_eq!(current_end, Some(105));
1239 assert_eq!(next_start, Some(305));
1240 });
1241 }
1242
1243 #[test_traced]
1244 fn test_prune_no_op() {
1245 let executor = deterministic::Runner::default();
1247 executor.start(|context| async move {
1248 let cfg = Config {
1249 partition: "test-ordinal".into(),
1250 items_per_blob: NZU64!(100),
1251 write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1252 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1253 };
1254
1255 let mut store =
1256 Ordinal::<_, FixedBytes<32>>::init(context.child("storage"), cfg.clone(), None)
1257 .await
1258 .expect("Failed to initialize store");
1259
1260 store = store.put(100, FixedBytes::new([100u8; 32])).await.unwrap();
1262 store = store.put(200, FixedBytes::new([200u8; 32])).await.unwrap();
1263 store = store.sync().await.unwrap();
1264
1265 store = store.prune(50).await.unwrap();
1267
1268 assert!(store.has(100));
1270 assert!(store.has(200));
1271 let buffer = context.encode();
1272 assert!(buffer.contains("pruned_total 0"));
1273
1274 store = store.prune(100).await.unwrap();
1276
1277 assert!(store.has(100));
1279 assert!(store.has(200));
1280 let buffer = context.encode();
1281 assert!(buffer.contains("pruned_total 0"));
1282 });
1283 }
1284
1285 #[test_traced]
1286 fn test_prune_empty_store() {
1287 let executor = deterministic::Runner::default();
1289 executor.start(|context| async move {
1290 let cfg = Config {
1291 partition: "test-ordinal".into(),
1292 items_per_blob: NZU64!(100),
1293 write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1294 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1295 };
1296
1297 let mut store =
1298 Ordinal::<_, FixedBytes<32>>::init(context.child("storage"), cfg.clone(), None)
1299 .await
1300 .expect("Failed to initialize store");
1301
1302 store = store.prune(1000).await.unwrap();
1304
1305 store = store.put(0, FixedBytes::new([0u8; 32])).await.unwrap();
1307 assert!(store.has(0));
1308 });
1309 }
1310
1311 #[test_traced]
1312 fn test_prune_after_restart() {
1313 let executor = deterministic::Runner::default();
1315 executor.start(|context| async move {
1316 let cfg = Config {
1317 partition: "test-ordinal".into(),
1318 items_per_blob: NZU64!(100),
1319 write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1320 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1321 };
1322
1323 {
1325 let mut store =
1326 Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
1327 .await
1328 .expect("Failed to initialize store");
1329
1330 store = store.put(0, FixedBytes::new([0u8; 32])).await.unwrap();
1331 store = store.put(100, FixedBytes::new([100u8; 32])).await.unwrap();
1332 store = store.put(200, FixedBytes::new([200u8; 32])).await.unwrap();
1333 store.sync().await.unwrap();
1334 }
1335
1336 {
1338 let mut bits0 = BitMap::zeroes(100);
1339 bits0.set(0, true);
1340 let mut bits1 = BitMap::zeroes(100);
1341 bits1.set(0, true);
1342 let mut bits2 = BitMap::zeroes(100);
1343 bits2.set(0, true);
1344 let bits0 = Some(bits0);
1345 let bits1 = Some(bits1);
1346 let bits2 = Some(bits2);
1347 let mut bits = BTreeMap::new();
1348 bits.insert(0, &bits0);
1349 bits.insert(1, &bits1);
1350 bits.insert(2, &bits2);
1351 let mut store = Ordinal::<_, FixedBytes<32>>::init(
1352 context.child("second"),
1353 cfg.clone(),
1354 Some(bits),
1355 )
1356 .await
1357 .expect("Failed to initialize store");
1358
1359 assert!(store.has(0));
1361 assert!(store.has(100));
1362 assert!(store.has(200));
1363
1364 store = store.prune(150).await.unwrap();
1366
1367 assert!(!store.has(0));
1369 assert!(store.has(100));
1370 assert!(store.has(200));
1371
1372 store.sync().await.unwrap();
1373 }
1374
1375 {
1377 let mut bits1 = BitMap::zeroes(100);
1378 bits1.set(0, true);
1379 let mut bits2 = BitMap::zeroes(100);
1380 bits2.set(0, true);
1381 let bits1 = Some(bits1);
1382 let bits2 = Some(bits2);
1383 let mut bits = BTreeMap::new();
1384 bits.insert(1, &bits1);
1385 bits.insert(2, &bits2);
1386 let store = Ordinal::<_, FixedBytes<32>>::init(
1387 context.child("third"),
1388 cfg.clone(),
1389 Some(bits),
1390 )
1391 .await
1392 .expect("Failed to initialize store");
1393
1394 assert!(!store.has(0));
1395 assert!(store.has(100));
1396 assert!(store.has(200));
1397
1398 let (current_end, next_start) = store.next_gap(0);
1400 assert!(current_end.is_none());
1401 assert_eq!(next_start, Some(100));
1402 }
1403 });
1404 }
1405
1406 #[test_traced]
1407 fn test_prune_multiple_operations() {
1408 let executor = deterministic::Runner::default();
1410 executor.start(|context| async move {
1411 let cfg = Config {
1412 partition: "test-ordinal".into(),
1413 items_per_blob: NZU64!(50), write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1415 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1416 };
1417
1418 let mut store =
1419 Ordinal::<_, FixedBytes<32>>::init(context.child("storage"), cfg.clone(), None)
1420 .await
1421 .expect("Failed to initialize store");
1422
1423 let mut values = Vec::new();
1425 for i in 0..10 {
1426 let index = i * 50 + 25; let value = FixedBytes::new([i as u8; 32]);
1428 store = store.put(index, value.clone()).await.unwrap();
1429 values.push((index, value));
1430 }
1431 store = store.sync().await.unwrap();
1432
1433 for i in 1..5 {
1435 let prune_index = i * 50 + 10;
1436 store = store.prune(prune_index).await.unwrap();
1437
1438 for (index, _) in &values {
1440 if *index < prune_index {
1441 assert!(!store.has(*index), "Index {index} should be pruned");
1442 } else {
1443 assert!(store.has(*index), "Index {index} should not be pruned");
1444 }
1445 }
1446 }
1447
1448 let buffer = context.encode();
1450 assert!(buffer.contains("pruned_total 4"));
1451
1452 for i in 4..10 {
1454 let index = i * 50 + 25;
1455 assert!(store.has(index));
1456 assert_eq!(
1457 store.get(index).await.unwrap().unwrap(),
1458 values[i as usize].1
1459 );
1460 }
1461 });
1462 }
1463
1464 #[test_traced]
1465 fn test_prune_blob_boundaries() {
1466 let executor = deterministic::Runner::default();
1468 executor.start(|context| async move {
1469 let cfg = Config {
1470 partition: "test-ordinal".into(),
1471 items_per_blob: NZU64!(100),
1472 write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1473 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1474 };
1475
1476 let mut store =
1477 Ordinal::<_, FixedBytes<32>>::init(context.child("storage"), cfg.clone(), None)
1478 .await
1479 .expect("Failed to initialize store");
1480
1481 store = store.put(0, FixedBytes::new([0u8; 32])).await.unwrap(); store = store.put(99, FixedBytes::new([99u8; 32])).await.unwrap(); store = store.put(100, FixedBytes::new([100u8; 32])).await.unwrap(); store = store.put(199, FixedBytes::new([199u8; 32])).await.unwrap(); store = store.put(200, FixedBytes::new([200u8; 32])).await.unwrap(); store = store.sync().await.unwrap();
1488
1489 store = store.prune(100).await.unwrap();
1493 assert!(!store.has(0));
1494 assert!(!store.has(99));
1495 assert!(store.has(100));
1496 assert!(store.has(199));
1497 assert!(store.has(200));
1498
1499 store = store.prune(199).await.unwrap();
1501 assert!(store.has(100));
1502 assert!(store.has(199));
1503 assert!(store.has(200));
1504
1505 store = store.prune(200).await.unwrap();
1507 assert!(!store.has(100));
1508 assert!(!store.has(199));
1509 assert!(store.has(200));
1510
1511 let buffer = context.encode();
1512 assert!(buffer.contains("pruned_total 2"));
1513 });
1514 }
1515
1516 #[test_traced]
1517 fn test_prune_non_contiguous_sections() {
1518 let executor = deterministic::Runner::default();
1520 executor.start(|context| async move {
1521 let cfg = Config {
1522 partition: "test-ordinal".into(),
1523 items_per_blob: NZU64!(100),
1524 write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1525 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1526 };
1527
1528 let mut store =
1529 Ordinal::<_, FixedBytes<32>>::init(context.child("storage"), cfg.clone(), None)
1530 .await
1531 .expect("Failed to initialize store");
1532
1533 store = store.put(0, FixedBytes::new([0u8; 32])).await.unwrap(); store = store.put(250, FixedBytes::new([50u8; 32])).await.unwrap(); store = store.put(500, FixedBytes::new([44u8; 32])).await.unwrap(); store = store.put(750, FixedBytes::new([45u8; 32])).await.unwrap(); store = store.sync().await.unwrap();
1539
1540 assert!(store.has(0));
1542 assert!(store.has(250));
1543 assert!(store.has(500));
1544 assert!(store.has(750));
1545
1546 store = store.prune(300).await.unwrap();
1548
1549 assert!(!store.has(0)); assert!(!store.has(250)); assert!(store.has(500)); assert!(store.has(750)); let buffer = context.encode();
1556 assert!(buffer.contains("pruned_total 2"));
1557
1558 store = store.prune(600).await.unwrap();
1560
1561 assert!(!store.has(500)); assert!(store.has(750)); let buffer = context.encode();
1566 assert!(buffer.contains("pruned_total 3"));
1567
1568 store = store.prune(1000).await.unwrap();
1570
1571 assert!(!store.has(750)); let buffer = context.encode();
1575 assert!(buffer.contains("pruned_total 4"));
1576 });
1577 }
1578
1579 #[test_traced]
1580 fn test_prune_removes_correct_pending() {
1581 let executor = deterministic::Runner::default();
1583 executor.start(|context| async move {
1584 let cfg = Config {
1585 partition: "test-ordinal".into(),
1586 items_per_blob: NZU64!(100),
1587 write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1588 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1589 };
1590 let mut store =
1591 Ordinal::<_, FixedBytes<32>>::init(context.child("storage"), cfg.clone(), None)
1592 .await
1593 .expect("Failed to initialize store");
1594
1595 store = store.put(5, FixedBytes::new([5u8; 32])).await.unwrap();
1597 store = store.sync().await.unwrap();
1598
1599 store = store.put(10, FixedBytes::new([10u8; 32])).await.unwrap(); store = store.put(110, FixedBytes::new([110u8; 32])).await.unwrap(); assert!(store.has(5));
1605 assert!(store.has(10));
1606 assert!(store.has(110));
1607
1608 store = store.prune(150).await.unwrap();
1610
1611 assert!(!store.has(5));
1613 assert!(!store.has(10));
1614
1615 assert!(store.has(110));
1617 assert_eq!(
1618 store.get(110).await.unwrap().unwrap(),
1619 FixedBytes::new([110u8; 32])
1620 );
1621
1622 store = store.sync().await.unwrap();
1624 assert!(store.has(110));
1625 assert_eq!(
1626 store.get(110).await.unwrap().unwrap(),
1627 FixedBytes::new([110u8; 32])
1628 );
1629 });
1630 }
1631
1632 #[test_traced]
1633 fn test_init_without_bits_deletes_existing_data() {
1634 let executor = deterministic::Runner::default();
1636 executor.start(|context| async move {
1637 let cfg = Config {
1638 partition: "test-ordinal".into(),
1639 items_per_blob: NZU64!(10), write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1641 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1642 };
1643
1644 {
1646 let mut store =
1647 Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
1648 .await
1649 .expect("Failed to initialize store");
1650
1651 store = store.put(0, FixedBytes::new([0u8; 32])).await.unwrap();
1653 store = store.put(5, FixedBytes::new([5u8; 32])).await.unwrap();
1654 store = store.put(9, FixedBytes::new([9u8; 32])).await.unwrap();
1655
1656 store = store.put(10, FixedBytes::new([10u8; 32])).await.unwrap();
1658 store = store.put(15, FixedBytes::new([15u8; 32])).await.unwrap();
1659
1660 store = store.put(25, FixedBytes::new([25u8; 32])).await.unwrap();
1662
1663 store.sync().await.unwrap();
1664 }
1665
1666 {
1668 let store =
1669 Ordinal::<_, FixedBytes<32>>::init(context.child("second"), cfg.clone(), None)
1670 .await
1671 .expect("Failed to initialize store");
1672
1673 assert!(!store.has(0));
1674 assert!(!store.has(5));
1675 assert!(!store.has(9));
1676 assert!(!store.has(10));
1677 assert!(!store.has(15));
1678 assert!(!store.has(25));
1679 assert!(!store.has(1));
1680 assert!(!store.has(11));
1681 assert!(!store.has(20));
1682 }
1683 });
1684 }
1685
1686 #[test_traced]
1687 fn test_init_empty_hashmap() {
1688 let executor = deterministic::Runner::default();
1690 executor.start(|context| async move {
1691 let cfg = Config {
1692 partition: "test-ordinal".into(),
1693 items_per_blob: NZU64!(10),
1694 write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1695 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1696 };
1697
1698 {
1700 let mut store =
1701 Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
1702 .await
1703 .expect("Failed to initialize store");
1704
1705 store = store.put(0, FixedBytes::new([0u8; 32])).await.unwrap();
1706 store = store.put(10, FixedBytes::new([10u8; 32])).await.unwrap();
1707 store = store.put(20, FixedBytes::new([20u8; 32])).await.unwrap();
1708
1709 store.sync().await.unwrap();
1710 }
1711
1712 {
1714 let bits: BTreeMap<u64, &Option<BitMap>> = BTreeMap::new();
1715 let store = Ordinal::<_, FixedBytes<32>>::init(
1716 context.child("second"),
1717 cfg.clone(),
1718 Some(bits),
1719 )
1720 .await
1721 .expect("Failed to initialize store with bits");
1722
1723 assert!(!store.has(0));
1725 assert!(!store.has(10));
1726 assert!(!store.has(20));
1727 }
1728
1729 {
1731 let mut section = BitMap::zeroes(10);
1732 section.set(0, true);
1733 let section = Some(section);
1734 let mut bits = BTreeMap::new();
1735 bits.insert(0, §ion);
1736 let result = Ordinal::<_, FixedBytes<32>>::init(
1737 context.child("third"),
1738 cfg.clone(),
1739 Some(bits),
1740 )
1741 .await;
1742 assert!(matches!(result, Err(Error::MissingRecord(0))));
1743 }
1744 });
1745 }
1746
1747 #[test_traced]
1748 fn test_init_selective_sections() {
1749 let executor = deterministic::Runner::default();
1751 executor.start(|context| async move {
1752 let cfg = Config {
1753 partition: "test-ordinal".into(),
1754 items_per_blob: NZU64!(10),
1755 write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1756 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1757 };
1758
1759 {
1761 let mut store =
1762 Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
1763 .await
1764 .expect("Failed to initialize store");
1765
1766 for i in 0..10 {
1768 store = store.put(i, FixedBytes::new([i as u8; 32])).await.unwrap();
1769 }
1770
1771 for i in 10..20 {
1773 store = store.put(i, FixedBytes::new([i as u8; 32])).await.unwrap();
1774 }
1775
1776 for i in 20..30 {
1778 store = store.put(i, FixedBytes::new([i as u8; 32])).await.unwrap();
1779 }
1780
1781 store.sync().await.unwrap();
1782 }
1783
1784 {
1786 let mut bits_map: BTreeMap<u64, &Option<BitMap>> = BTreeMap::new();
1787
1788 let mut bitmap = BitMap::zeroes(10);
1790 bitmap.set(2, true); bitmap.set(5, true); bitmap.set(8, true); let bitmap_option = Some(bitmap);
1794
1795 bits_map.insert(1, &bitmap_option);
1796
1797 let store = Ordinal::<_, FixedBytes<32>>::init(
1798 context.child("second"),
1799 cfg.clone(),
1800 Some(bits_map),
1801 )
1802 .await
1803 .expect("Failed to initialize store with bits");
1804
1805 assert!(store.has(12));
1807 assert!(store.has(15));
1808 assert!(store.has(18));
1809
1810 assert!(!store.has(10));
1812 assert!(!store.has(11));
1813 assert!(!store.has(13));
1814 assert!(!store.has(14));
1815 assert!(!store.has(16));
1816 assert!(!store.has(17));
1817 assert!(!store.has(19));
1818
1819 for i in 0..10 {
1821 assert!(!store.has(i));
1822 }
1823 for i in 20..30 {
1824 assert!(!store.has(i));
1825 }
1826
1827 assert_eq!(
1829 store.get(12).await.unwrap().unwrap(),
1830 FixedBytes::new([12u8; 32])
1831 );
1832 assert_eq!(
1833 store.get(15).await.unwrap().unwrap(),
1834 FixedBytes::new([15u8; 32])
1835 );
1836 assert_eq!(
1837 store.get(18).await.unwrap().unwrap(),
1838 FixedBytes::new([18u8; 32])
1839 );
1840 }
1841
1842 {
1846 let mut bitmap = BitMap::zeroes(10);
1847 bitmap.set(0, true); let bitmap_option = Some(bitmap);
1849 let mut bits_map: BTreeMap<u64, &Option<BitMap>> = BTreeMap::new();
1850 bits_map.insert(1, &bitmap_option);
1851 let store = Ordinal::<_, FixedBytes<32>>::init(
1852 context.child("third"),
1853 cfg.clone(),
1854 Some(bits_map),
1855 )
1856 .await
1857 .expect("Failed to initialize store with bits");
1858 assert!(store.has(10));
1859 assert!(matches!(store.get(10).await, Err(Error::InvalidRecord(10))));
1860 }
1861 });
1862 }
1863
1864 #[test_traced]
1865 fn test_init_none_option_all_records_exist() {
1866 let executor = deterministic::Runner::default();
1868 executor.start(|context| async move {
1869 let cfg = Config {
1870 partition: "test-ordinal".into(),
1871 items_per_blob: NZU64!(5),
1872 write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1873 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1874 };
1875
1876 {
1878 let mut store =
1879 Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
1880 .await
1881 .expect("Failed to initialize store");
1882
1883 for i in 5..10 {
1885 store = store.put(i, FixedBytes::new([i as u8; 32])).await.unwrap();
1886 }
1887
1888 store.sync().await.unwrap();
1889 }
1890
1891 {
1893 let mut bits_map: BTreeMap<u64, &Option<BitMap>> = BTreeMap::new();
1894 let none_option: Option<BitMap> = None;
1895 bits_map.insert(1, &none_option);
1896
1897 let store = Ordinal::<_, FixedBytes<32>>::init(
1898 context.child("second"),
1899 cfg.clone(),
1900 Some(bits_map),
1901 )
1902 .await
1903 .expect("Failed to initialize store with bits");
1904
1905 for i in 5..10 {
1907 assert!(store.has(i));
1908 assert_eq!(
1909 store.get(i).await.unwrap().unwrap(),
1910 FixedBytes::new([i as u8; 32])
1911 );
1912 }
1913 }
1914 });
1915 }
1916
1917 #[test_traced]
1918 #[should_panic(expected = "Failed to initialize store with bits: MissingRecord(6)")]
1919 fn test_init_none_option_missing_record_panics() {
1920 let executor = deterministic::Runner::default();
1922 executor.start(|context| async move {
1923 let cfg = Config {
1924 partition: "test-ordinal".into(),
1925 items_per_blob: NZU64!(5),
1926 write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1927 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1928 };
1929
1930 {
1932 let mut store =
1933 Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
1934 .await
1935 .expect("Failed to initialize store");
1936
1937 store = store.put(5, FixedBytes::new([5u8; 32])).await.unwrap();
1939 store = store.put(7, FixedBytes::new([7u8; 32])).await.unwrap();
1941 store = store.put(8, FixedBytes::new([8u8; 32])).await.unwrap();
1942 store = store.put(9, FixedBytes::new([9u8; 32])).await.unwrap();
1943
1944 store.sync().await.unwrap();
1945 }
1946
1947 {
1950 let mut bits_map: BTreeMap<u64, &Option<BitMap>> = BTreeMap::new();
1951 let none_option: Option<BitMap> = None;
1952 bits_map.insert(1, &none_option);
1953
1954 let _store = Ordinal::<_, FixedBytes<32>>::init(
1955 context.child("second"),
1956 cfg.clone(),
1957 Some(bits_map),
1958 )
1959 .await
1960 .expect("Failed to initialize store with bits");
1961 }
1962 });
1963 }
1964
1965 #[test_traced]
1966 fn test_init_mixed_sections() {
1967 let executor = deterministic::Runner::default();
1969 executor.start(|context| async move {
1970 let cfg = Config {
1971 partition: "test-ordinal".into(),
1972 items_per_blob: NZU64!(5),
1973 write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1974 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1975 };
1976
1977 {
1979 let mut store =
1980 Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
1981 .await
1982 .expect("Failed to initialize store");
1983
1984 for i in 0..5 {
1986 store = store.put(i, FixedBytes::new([i as u8; 32])).await.unwrap();
1987 }
1988
1989 store = store.put(5, FixedBytes::new([5u8; 32])).await.unwrap();
1991 store = store.put(7, FixedBytes::new([7u8; 32])).await.unwrap();
1992 store = store.put(9, FixedBytes::new([9u8; 32])).await.unwrap();
1993
1994 for i in 10..15 {
1996 store = store.put(i, FixedBytes::new([i as u8; 32])).await.unwrap();
1997 }
1998
1999 store.sync().await.unwrap();
2000 }
2001
2002 {
2004 let mut bits_map: BTreeMap<u64, &Option<BitMap>> = BTreeMap::new();
2005
2006 let none_option: Option<BitMap> = None;
2008 bits_map.insert(0, &none_option);
2009
2010 let mut bitmap1 = BitMap::zeroes(5);
2012 bitmap1.set(0, true); bitmap1.set(2, true); let bitmap1_option = Some(bitmap1);
2016 bits_map.insert(1, &bitmap1_option);
2017
2018 let store = Ordinal::<_, FixedBytes<32>>::init(
2021 context.child("second"),
2022 cfg.clone(),
2023 Some(bits_map),
2024 )
2025 .await
2026 .expect("Failed to initialize store with bits");
2027
2028 for i in 0..5 {
2030 assert!(store.has(i));
2031 assert_eq!(
2032 store.get(i).await.unwrap().unwrap(),
2033 FixedBytes::new([i as u8; 32])
2034 );
2035 }
2036
2037 assert!(store.has(5));
2039 assert!(store.has(7));
2040 assert!(!store.has(6));
2041 assert!(!store.has(8));
2042 assert!(!store.has(9)); for i in 10..15 {
2046 assert!(!store.has(i));
2047 }
2048 }
2049 });
2050 }
2051
2052 #[test_traced]
2053 fn test_marked_record_damage_surfaces_at_get() {
2054 let executor = deterministic::Runner::default();
2056 executor.start(|context| async move {
2057 let cfg = Config {
2058 partition: "test-ordinal".into(),
2059 items_per_blob: NZU64!(5),
2060 write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
2061 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
2062 };
2063
2064 {
2066 let mut store =
2067 Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
2068 .await
2069 .expect("Failed to initialize store");
2070
2071 for i in 0..5 {
2073 store = store.put(i, FixedBytes::new([i as u8; 32])).await.unwrap();
2074 }
2075
2076 store.sync().await.unwrap();
2077 }
2078
2079 {
2081 let (blob, _) = context
2082 .open("test-ordinal", &0u64.to_be_bytes())
2083 .await
2084 .unwrap();
2085 let offset = 2 * 36 + 32; blob.write_at(offset, vec![0xFF], WriteOptions::SYNC)
2088 .await
2089 .unwrap();
2090 }
2091
2092 {
2094 let mut bits_map: BTreeMap<u64, &Option<BitMap>> = BTreeMap::new();
2095
2096 let mut bitmap = BitMap::zeroes(5);
2100 bitmap.set(0, true); bitmap.set(2, true); bitmap.set(4, true); let bitmap_option = Some(bitmap);
2104 bits_map.insert(0, &bitmap_option);
2105
2106 let store = Ordinal::<_, FixedBytes<32>>::init(
2107 context.child("second"),
2108 cfg.clone(),
2109 Some(bits_map),
2110 )
2111 .await
2112 .expect("Failed to initialize store with bits");
2113 assert_eq!(
2114 store.get(0).await.unwrap(),
2115 Some(FixedBytes::new([0u8; 32]))
2116 );
2117 assert!(store.get(2).await.is_err());
2118 assert_eq!(
2119 store.get(4).await.unwrap(),
2120 Some(FixedBytes::new([4u8; 32]))
2121 );
2122 }
2123 });
2124 }
2125
2126 #[derive(Debug, PartialEq, Eq)]
2128 pub struct DummyValue {
2129 pub value: u64,
2130 }
2131
2132 impl Write for DummyValue {
2133 fn write(&self, buf: &mut impl BufMut) {
2134 self.value.write(buf);
2135 }
2136 }
2137
2138 impl Read for DummyValue {
2139 type Cfg = ();
2140
2141 fn read_cfg(buf: &mut impl Buf, _: &Self::Cfg) -> Result<Self, commonware_codec::Error> {
2142 let value = u64::read(buf)?;
2143 if value == 0 {
2144 return Err(commonware_codec::Error::Invalid(
2145 "DummyValue",
2146 "value must be non-zero",
2147 ));
2148 }
2149 Ok(Self { value })
2150 }
2151 }
2152
2153 impl FixedSize for DummyValue {
2154 const SIZE: usize = u64::SIZE;
2155 }
2156
2157 #[test_traced]
2158 fn test_init_skip_unparseable_record() {
2159 let executor = deterministic::Runner::default();
2161 executor.start(|context| async move {
2162 let cfg = Config {
2163 partition: "test-ordinal".into(),
2164 items_per_blob: NZU64!(1),
2165 write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
2166 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
2167 };
2168
2169 {
2171 let mut store =
2172 Ordinal::<_, DummyValue>::init(context.child("first"), cfg.clone(), None)
2173 .await
2174 .expect("Failed to initialize store");
2175
2176 store = store.put(1, DummyValue { value: 1 }).await.unwrap();
2178 store = store.put(2, DummyValue { value: 0 }).await.unwrap(); store = store.put(4, DummyValue { value: 4 }).await.unwrap();
2180
2181 store = store.sync().await.unwrap();
2182
2183 assert!(matches!(store.get(2).await, Err(Error::InvalidRecord(2))));
2185 }
2186
2187 {
2189 let store =
2190 Ordinal::<_, DummyValue>::init(context.child("second"), cfg.clone(), None)
2191 .await
2192 .expect("Failed to initialize store");
2193
2194 assert!(!store.has(1));
2195 assert!(!store.has(2));
2196 assert!(!store.has(4));
2197 }
2198 });
2199 }
2200
2201 fn config() -> Config {
2203 Config {
2204 partition: "test-ordinal".into(),
2205 items_per_blob: NZU64!(2),
2206 write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
2207 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
2208 }
2209 }
2210
2211 async fn fill(context: deterministic::Context, cfg: &Config) {
2213 let mut store = Ordinal::<_, FixedBytes<32>>::init(context, cfg.clone(), None)
2214 .await
2215 .expect("Failed to initialize store");
2216 for i in 0..4 {
2217 store = store.put(i, FixedBytes::new([i as u8; 32])).await.unwrap();
2218 }
2219 store.sync().await.expect("Failed to sync data");
2220 }
2221
2222 async fn tear(context: &deterministic::Context, cfg: &Config, section: u64) {
2224 let (blob, len) = context
2225 .open(&cfg.partition, §ion.to_be_bytes())
2226 .await
2227 .expect("Failed to open blob");
2228 blob.resize(len - 1).await.expect("Failed to tear tail");
2229 blob.sync().await.expect("Failed to sync torn tail");
2230 }
2231
2232 async fn recover(
2235 context: deterministic::Context,
2236 cfg: &Config,
2237 bits: BTreeMap<u64, &Option<BitMap>>,
2238 ) -> (
2239 Ordinal<DelayedSyncContext<deterministic::Context>, FixedBytes<32>>,
2240 usize,
2241 ) {
2242 let pending = PendingSyncs::default();
2245 pending.arm();
2246 let delayed = DelayedSyncContext {
2247 inner: context,
2248 pending: pending.clone(),
2249 };
2250 let store = drive_pending_syncs(&pending, Ordinal::init(delayed, cfg.clone(), Some(bits)))
2251 .await
2252 .expect("Failed to initialize store");
2253 (store, pending.calls())
2254 }
2255
2256 #[test_traced]
2258 fn test_init_clean_sections_sync_nothing() {
2259 let executor = deterministic::Runner::default();
2261 executor.start(|context| async move {
2262 let cfg = config();
2264 fill(context.child("first"), &cfg).await;
2265
2266 let none = None;
2268 let bits = BTreeMap::from([(0, &none), (1, &none)]);
2269 let (store, calls) = recover(context.child("second"), &cfg, bits).await;
2270
2271 assert_eq!(calls, 0);
2273 for i in 0..4 {
2274 assert!(store.has(i));
2275 }
2276 });
2277 }
2278
2279 #[rstest::rstest]
2282 #[case::omitted(false)]
2283 #[case::unmarked(true)]
2284 #[test_traced]
2285 fn test_init_removes_uncovered_torn_section_unopened(#[case] unmarked: bool) {
2286 let executor = deterministic::Runner::default();
2288 executor.start(|context| async move {
2289 let cfg = config();
2291 fill(context.child("first"), &cfg).await;
2292 tear(&context, &cfg, 1).await;
2293
2294 let (held, _) = context
2296 .open(&cfg.partition, &1u64.to_be_bytes())
2297 .await
2298 .expect("Failed to open blob");
2299
2300 let none = None;
2302 let zeroes = Some(BitMap::zeroes(2));
2303 let mut bits = BTreeMap::from([(0, &none)]);
2304 if unmarked {
2305 bits.insert(1, &zeroes);
2306 }
2307 let (store, calls) = recover(context.child("second"), &cfg, bits).await;
2308
2309 assert_eq!(calls, 0);
2312 assert!(store.has(0));
2313 assert!(store.has(1));
2314 assert!(!store.has(2));
2315 assert!(!store.has(3));
2316 assert_eq!(
2317 store.get(1).await.unwrap().unwrap(),
2318 FixedBytes::new([1u8; 32])
2319 );
2320 assert_eq!(
2321 context.scan(&cfg.partition).await.unwrap(),
2322 vec![0u64.to_be_bytes().to_vec()]
2323 );
2324 drop(held);
2325 });
2326 }
2327
2328 #[test_traced]
2330 fn test_init_ignores_uncovered_torn_section_sync_fault() {
2331 let executor = deterministic::Runner::default();
2333 executor.start(|context| async move {
2334 let cfg = config();
2336 fill(context.child("first"), &cfg).await;
2337 tear(&context, &cfg, 1).await;
2338
2339 let faulty = SyncFaultContext {
2341 inner: context.child("second"),
2342 fail_partition: cfg.partition.clone(),
2343 };
2344
2345 let none = None;
2347 let bits = BTreeMap::from([(0, &none)]);
2348 let store = Ordinal::<_, FixedBytes<32>>::init(faulty, cfg.clone(), Some(bits))
2349 .await
2350 .expect("Failed to initialize store");
2351
2352 assert!(store.has(0));
2354 assert!(store.has(1));
2355 assert!(!store.has(2));
2356 assert!(!store.has(3));
2357 assert_eq!(
2358 context.scan(&cfg.partition).await.unwrap(),
2359 vec![0u64.to_be_bytes().to_vec()]
2360 );
2361 });
2362 }
2363
2364 #[test_traced]
2366 fn test_init_repairs_covered_torn_section() {
2367 let executor = deterministic::Runner::default();
2369 executor.start(|context| async move {
2370 let cfg = config();
2372 fill(context.child("first"), &cfg).await;
2373 tear(&context, &cfg, 0).await;
2374
2375 let mut bitmap = BitMap::zeroes(2);
2377 bitmap.set(0, true);
2378 let partial = Some(bitmap);
2379 let none = None;
2380 let bits = BTreeMap::from([(0, &partial), (1, &none)]);
2381 let (store, calls) = recover(context.child("second"), &cfg, bits).await;
2382
2383 assert_eq!(calls, 1);
2386 assert!(store.has(0));
2387 assert!(!store.has(1));
2388 assert!(store.has(2));
2389 assert!(store.has(3));
2390 assert_eq!(
2391 store.get(0).await.unwrap().unwrap(),
2392 FixedBytes::new([0u8; 32])
2393 );
2394 drop(store);
2395
2396 let (_, len) = context
2398 .open(&cfg.partition, &0u64.to_be_bytes())
2399 .await
2400 .expect("Failed to open blob");
2401 assert_eq!(len, (FixedBytes::<32>::SIZE + u32::SIZE) as u64);
2402 });
2403 }
2404
2405 #[test_traced]
2408 fn test_init_rejects_whole_section_with_extra_records() {
2409 let executor = deterministic::Runner::default();
2410 executor.start(|context| async move {
2411 let mut cfg = Config {
2413 partition: "test-ordinal-whole".into(),
2414 items_per_blob: NZU64!(8),
2415 write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
2416 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
2417 };
2418 let mut store =
2419 Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
2420 .await
2421 .expect("Failed to initialize store");
2422 for i in 0..8 {
2423 store = store.put(i, FixedBytes::new([i as u8; 32])).await.unwrap();
2424 }
2425 store.sync().await.expect("Failed to sync data");
2426
2427 cfg.items_per_blob = NZU64!(4);
2429 let none = None;
2430 let bits = BTreeMap::from([(0, &none)]);
2431 let result = Ordinal::<_, FixedBytes<32>>::init(
2432 context.child("second"),
2433 cfg.clone(),
2434 Some(bits),
2435 )
2436 .await;
2437 assert!(matches!(result, Err(Error::InvalidRecord(4))), "{result:?}");
2438
2439 cfg.items_per_blob = NZU64!(8);
2441 let bits = BTreeMap::from([(0, &none)]);
2442 let store = Ordinal::<_, FixedBytes<32>>::init(context.child("third"), cfg, Some(bits))
2443 .await
2444 .expect("Failed to initialize store");
2445 for i in 0..8 {
2446 assert!(store.has(i));
2447 }
2448 });
2449 }
2450
2451 async fn fill_sparse(context: deterministic::Context, write_buffer: usize) -> Config {
2453 let cfg = Config {
2454 partition: "test-ordinal-sparse".into(),
2455 items_per_blob: NZU64!(DEFAULT_ITEMS_PER_BLOB),
2456 write_buffer: NonZeroUsize::new(write_buffer).unwrap(),
2457 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
2458 };
2459 let mut store = Ordinal::<_, FixedBytes<32>>::init(context, cfg.clone(), None)
2460 .await
2461 .expect("Failed to initialize store");
2462 store = store.put(1, FixedBytes::new([1u8; 32])).await.unwrap();
2463 store = store.put(900, FixedBytes::new([9u8; 32])).await.unwrap();
2464 store.sync().await.expect("Failed to sync data");
2465 cfg
2466 }
2467
2468 #[rstest::rstest]
2471 #[case::one_write_per_run(36 * DEFAULT_ITEMS_PER_BLOB as usize, 2)]
2472 #[case::chunked_runs(36 * 100, 10)]
2473 #[test_traced]
2474 fn test_init_clears_unmarked_runs(#[case] write_buffer: usize, #[case] writes: usize) {
2475 let executor = deterministic::Runner::default();
2476 executor.start(|context| async move {
2477 let cfg = fill_sparse(context.child("first"), write_buffer).await;
2478
2479 let mut bitmap = BitMap::zeroes(DEFAULT_ITEMS_PER_BLOB);
2481 bitmap.set(1, true);
2482 bitmap.set(900, true);
2483 let marked = Some(bitmap);
2484 let bits = BTreeMap::from([(0, &marked)]);
2485 let (recording, recordings) = RecordingContext::new(context.child("second"));
2486 let store = Ordinal::<_, FixedBytes<32>>::init(recording, cfg, Some(bits))
2487 .await
2488 .expect("Failed to initialize store");
2489
2490 assert_eq!(recordings.snapshot().writes.len(), writes);
2492 assert!(store.has(1));
2493 assert!(store.has(900));
2494 assert!(!store.has(0));
2495 assert!(!store.has(2));
2496 assert!(!store.has(899));
2497 });
2498 }
2499
2500 #[test_traced]
2503 fn test_init_clears_unmarked_records() {
2504 let executor = deterministic::Runner::default();
2505 executor.start(|context| async move {
2506 let cfg = Config {
2508 partition: "test-ordinal-dense".into(),
2509 items_per_blob: NZU64!(DEFAULT_ITEMS_PER_BLOB),
2510 write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
2511 replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
2512 };
2513 let mut store =
2514 Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
2515 .await
2516 .expect("Failed to initialize store");
2517 for i in 0..=900u64 {
2518 store = store.put(i, FixedBytes::new([i as u8; 32])).await.unwrap();
2519 }
2520 store.sync().await.expect("Failed to sync data");
2521
2522 let mut bitmap = BitMap::zeroes(500);
2524 bitmap.set(1, true);
2525 let marked = Some(bitmap);
2526 let bits = BTreeMap::from([(0, &marked)]);
2527 let (store, calls) = recover(context.child("second"), &cfg, bits).await;
2528
2529 assert_eq!(calls, 1);
2531 assert!(store.has(1));
2532 assert!(!store.has(5));
2533 assert!(!store.has(900));
2534 drop(store);
2535
2536 let record_size = FixedBytes::<32>::SIZE + u32::SIZE;
2538 let (blob, len) = context
2539 .open(&cfg.partition, &0u64.to_be_bytes())
2540 .await
2541 .expect("Failed to open blob");
2542 let bytes = blob
2543 .read_at(0, usize::try_from(len).unwrap(), ReadOptions::default())
2544 .await
2545 .unwrap()
2546 .coalesce();
2547 assert_eq!(bytes.len(), 901 * record_size);
2548 for (slot, record) in bytes.as_ref().chunks(record_size).enumerate() {
2549 if slot == 1 {
2550 assert_eq!(&record[..32], &[1u8; 32]);
2551 } else {
2552 assert!(record.iter().all(|byte| *byte == 0), "slot {slot}");
2553 }
2554 }
2555 });
2556 }
2557}