Skip to main content

commonware_storage/ordinal/
mod.rs

1//! A persistent index that maps sparse indices to [commonware_utils::Array]s.
2//!
3//! [Ordinal] is a collection of [commonware_runtime::Blob]s containing ordered records of fixed size.
4//! Because records are fixed size, file position corresponds directly to index. Unlike
5//! [crate::journal::contiguous::fixed::Journal], [Ordinal] supports out-of-order insertion.
6//!
7//! # Design
8//!
9//! [Ordinal] is a collection of [commonware_runtime::Blob]s where:
10//! - Each record: `[V][crc32(V)]` where V is an [commonware_utils::Array]
11//! - Index N is at file offset: `N * RECORD_SIZE`
12//! - A [crate::rmap::RMap] tracks which indices have been written (and which are missing)
13//!
14//! # File Organization
15//!
16//! Records are grouped into blobs to avoid having too many files:
17//!
18//! ```text
19//! Blob 0: indices 0-999
20//! Blob 1: indices 1000-1999
21//! ...
22//! ```
23//!
24//! # Format
25//!
26//! [Ordinal] stores values in the following format:
27//!
28//! ```text
29//! +---+---+---+---+---+---+---+---+---+---+---+---+---+
30//! | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |10 |11 |12 |
31//! +---+---+---+---+---+---+---+---+---+---+---+---+---+
32//! |          Value (Fixed Size)       |     CRC32     |
33//! +---+---+---+---+---+---+---+---+---+---+---+---+---+
34//! ```
35//!
36//! # Performance Characteristics
37//!
38//! - **Writes**: O(1) - direct offset calculation
39//! - **Reads**: O(1) - direct offset calculation
40//! - **Has**: O(1) - in-memory lookup (via [crate::rmap::RMap])
41//! - **Next Gap**: O(log n) - in-memory range query (via [crate::rmap::RMap])
42//! - **Recovery**: O(n) over committed records when bits are provided (`None` resets the store)
43//!
44//! # Atomicity
45//!
46//! [Ordinal] eagerly writes all new data to [commonware_runtime::Blob]s. New data, however, is not
47//! synced until [Ordinal::sync] is called. As a result, data is not guaranteed to be atomically
48//! persisted (i.e. shutdown before [Ordinal::sync] may lead to some writes being lost).
49//!
50//! _If you want atomicity for sparse writes, pair [commonware_utils::bitmap::BitMap] and
51//! [crate::metadata::Metadata] with [Ordinal] (use bits to indicate which items have been atomically
52//! written)._
53//!
54//! # Recovery
55//!
56//! To recover existing data, pass `Some(bits)` to [Ordinal::init]. The bits identify which records
57//! were durably committed by the caller. [Ordinal] validates required records using their CRC32 and
58//! rebuilds the in-memory [crate::rmap::RMap]. Stored sections omitted from `bits` are removed, and
59//! stored records whose bits are unset are cleared before replay. Missing or invalid required records
60//! fail initialization. Passing `Some(BTreeMap::new())` or `None` removes all stored sections and
61//! starts empty.
62//!
63//! # Example
64//!
65//! ```rust
66//! use commonware_runtime::{Spawner, Runner, deterministic};
67//! use commonware_storage::ordinal::{Ordinal, Config};
68//! use commonware_utils::{sequence::FixedBytes, NZUsize, NZU64};
69//!
70//! let executor = deterministic::Runner::default();
71//! executor.start(|context| async move {
72//!     // Create a store for 32-byte values
73//!     let cfg = Config {
74//!         partition: "ordinal-store".into(),
75//!         items_per_blob: NZU64!(10000),
76//!         write_buffer: NZUsize!(4096),
77//!         replay_buffer: NZUsize!(1024 * 1024),
78//!     };
79//!     let mut store = Ordinal::<_, FixedBytes<32>>::init(context, cfg, None).await.unwrap();
80//!
81//!     // Put values at specific indices
82//!     let value1 = FixedBytes::new([1u8; 32]);
83//!     let value2 = FixedBytes::new([2u8; 32]);
84//!     store.put(0, value1).await.unwrap();
85//!     store.put(5, value2).await.unwrap();
86//!
87//!     // Sync to disk
88//!     store.sync().await.unwrap();
89//!
90//!     // Check for gaps
91//!     let (current_end, next_start) = store.next_gap(0);
92//!     assert_eq!(current_end, Some(0));
93//!     assert_eq!(next_start, Some(5));
94//!
95//!     // Sync the store
96//!     store.sync().await.unwrap();
97//! });
98//! ```
99
100#[cfg(all(test, feature = "arbitrary"))]
101mod conformance;
102mod storage;
103
104use std::num::{NonZeroU64, NonZeroUsize};
105pub use storage::Ordinal;
106use thiserror::Error;
107
108/// Errors that can occur when interacting with the [Ordinal].
109#[derive(Debug, Error)]
110pub enum Error {
111    #[error("runtime error: {0}")]
112    Runtime(#[from] commonware_runtime::Error),
113    #[error("codec error: {0}")]
114    Codec(#[from] commonware_codec::Error),
115    #[error("invalid blob name: {0}")]
116    InvalidBlobName(String),
117    #[error("invalid record: {0}")]
118    InvalidRecord(u64),
119    #[error("missing record at {0}")]
120    MissingRecord(u64),
121}
122
123/// Configuration for [Ordinal] storage.
124#[derive(Clone)]
125pub struct Config {
126    /// The [commonware_runtime::Storage] partition to use for storing the index.
127    pub partition: String,
128
129    /// The maximum number of items to store in each index blob.
130    pub items_per_blob: NonZeroU64,
131
132    /// The size of the write buffer to use when writing to the index.
133    pub write_buffer: NonZeroUsize,
134
135    /// The size of the read buffer to use on restart.
136    pub replay_buffer: NonZeroUsize,
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142    use crate::utils::bits_for_indices;
143    use commonware_codec::{FixedSize, Read, ReadExt, Write};
144    use commonware_cryptography::Crc32;
145    use commonware_formatting::hex;
146    use commonware_macros::{test_group, test_traced};
147    use commonware_runtime::{
148        deterministic, Blob, Buf, BufMut, Metrics as _, Runner, Storage, Supervisor as _,
149    };
150    use commonware_utils::{bitmap::BitMap, sequence::FixedBytes, NZUsize, NZU64};
151    use rand::Rng;
152    use std::collections::BTreeMap;
153
154    const DEFAULT_ITEMS_PER_BLOB: u64 = 1000;
155    const DEFAULT_WRITE_BUFFER: usize = 4096;
156    const DEFAULT_REPLAY_BUFFER: usize = 1024 * 1024;
157
158    #[test_traced]
159    fn test_put_get() {
160        // Initialize the deterministic context
161        let executor = deterministic::Runner::default();
162        executor.start(|context| async move {
163            // Initialize the store
164            let cfg = Config {
165                partition: "test-ordinal".into(),
166                items_per_blob: NZU64!(DEFAULT_ITEMS_PER_BLOB),
167                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
168                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
169            };
170            let mut store =
171                Ordinal::<_, FixedBytes<32>>::init(context.child("storage"), cfg.clone(), None)
172                    .await
173                    .expect("Failed to initialize store");
174
175            let value = FixedBytes::new([42u8; 32]);
176
177            // Check index doesn't exist
178            assert!(!store.has(0));
179
180            // Put the value at index 0
181            store
182                .put(0, value.clone())
183                .await
184                .expect("Failed to put data");
185
186            // Check index exists
187            assert!(store.has(0));
188
189            // Get the value back (before sync)
190            let retrieved = store
191                .get(0)
192                .await
193                .expect("Failed to get data")
194                .expect("Data not found");
195            assert_eq!(retrieved, value);
196
197            // Force a sync
198            store.sync().await.expect("Failed to sync data");
199
200            // Check metrics
201            let buffer = context.encode();
202            assert!(buffer.contains("gets_total 1"), "{}", buffer);
203            assert!(buffer.contains("puts_total 1"), "{}", buffer);
204            assert!(buffer.contains("has_total 2"), "{}", buffer);
205            assert!(buffer.contains("syncs_total 1"), "{}", buffer);
206            assert!(buffer.contains("pruned_total 0"), "{}", buffer);
207
208            // Get the value back (after sync)
209            let retrieved = store
210                .get(0)
211                .await
212                .expect("Failed to get data")
213                .expect("Data not found");
214            assert_eq!(retrieved, value);
215        });
216    }
217
218    #[test_traced]
219    fn test_sync_does_not_report_success_while_flush_fails() {
220        let executor = deterministic::Runner::default();
221        executor.start(|context| async move {
222            let cfg = Config {
223                partition: "test-ordinal".into(),
224                items_per_blob: NZU64!(DEFAULT_ITEMS_PER_BLOB),
225                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
226                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
227            };
228            let mut store =
229                Ordinal::<_, FixedBytes<32>>::init(context.child("storage"), cfg.clone(), None)
230                    .await
231                    .expect("Failed to initialize store");
232
233            store
234                .put(0, FixedBytes::new([42u8; 32]))
235                .await
236                .expect("Failed to put data");
237
238            // Force flush failure by removing the underlying blob before sync.
239            let section = 0u64.to_be_bytes();
240            context
241                .remove(&cfg.partition, Some(&section))
242                .await
243                .expect("Failed to remove blob");
244
245            // Sync must observe the durability failure.
246            assert!(store.sync().await.is_err(), "sync unexpectedly succeeded");
247        });
248    }
249
250    #[test_traced]
251    fn test_multiple_indices() {
252        // Initialize the deterministic context
253        let executor = deterministic::Runner::default();
254        executor.start(|context| async move {
255            // Initialize the store
256            let cfg = Config {
257                partition: "test-ordinal".into(),
258                items_per_blob: NZU64!(DEFAULT_ITEMS_PER_BLOB),
259                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
260                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
261            };
262            let mut store =
263                Ordinal::<_, FixedBytes<32>>::init(context.child("storage"), cfg.clone(), None)
264                    .await
265                    .expect("Failed to initialize store");
266
267            // Insert multiple values at different indices
268            let indices = vec![
269                (0u64, FixedBytes::new([0u8; 32])),
270                (5u64, FixedBytes::new([5u8; 32])),
271                (10u64, FixedBytes::new([10u8; 32])),
272                (100u64, FixedBytes::new([100u8; 32])),
273                (1000u64, FixedBytes::new([200u8; 32])), // Different blob
274            ];
275
276            for (index, value) in &indices {
277                store
278                    .put(*index, value.clone())
279                    .await
280                    .expect("Failed to put data");
281            }
282
283            // Sync to disk
284            store.sync().await.expect("Failed to sync");
285
286            // Retrieve all values and verify
287            for (index, value) in &indices {
288                let retrieved = store
289                    .get(*index)
290                    .await
291                    .expect("Failed to get data")
292                    .expect("Data not found");
293                assert_eq!(&retrieved, value);
294            }
295        });
296    }
297
298    #[test_traced]
299    fn test_sparse_indices() {
300        // Initialize the deterministic context
301        let executor = deterministic::Runner::default();
302        executor.start(|context| async move {
303            // Initialize the store
304            let cfg = Config {
305                partition: "test-ordinal".into(),
306                items_per_blob: NZU64!(100), // Smaller blobs for testing
307                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
308                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
309            };
310            let mut store =
311                Ordinal::<_, FixedBytes<32>>::init(context.child("storage"), cfg.clone(), None)
312                    .await
313                    .expect("Failed to initialize store");
314
315            // Insert sparse values
316            let indices = vec![
317                (0u64, FixedBytes::new([0u8; 32])),
318                (99u64, FixedBytes::new([99u8; 32])), // End of first blob
319                (100u64, FixedBytes::new([100u8; 32])), // Start of second blob
320                (500u64, FixedBytes::new([200u8; 32])), // Start of sixth blob
321            ];
322
323            for (index, value) in &indices {
324                store
325                    .put(*index, value.clone())
326                    .await
327                    .expect("Failed to put data");
328            }
329
330            // Check that intermediate indices don't exist
331            assert!(!store.has(1));
332            assert!(!store.has(50));
333            assert!(!store.has(101));
334            assert!(!store.has(499));
335
336            // Sync and verify
337            store.sync().await.expect("Failed to sync");
338
339            for (index, value) in &indices {
340                let retrieved = store
341                    .get(*index)
342                    .await
343                    .expect("Failed to get data")
344                    .expect("Data not found");
345                assert_eq!(&retrieved, value);
346            }
347        });
348    }
349
350    #[test_traced]
351    fn test_next_gap() {
352        // Initialize the deterministic context
353        let executor = deterministic::Runner::default();
354        executor.start(|context| async move {
355            // Initialize the store
356            let cfg = Config {
357                partition: "test-ordinal".into(),
358                items_per_blob: NZU64!(DEFAULT_ITEMS_PER_BLOB),
359                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
360                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
361            };
362            let mut store =
363                Ordinal::<_, FixedBytes<32>>::init(context.child("storage"), cfg.clone(), None)
364                    .await
365                    .expect("Failed to initialize store");
366
367            // Insert values with gaps
368            store.put(1, FixedBytes::new([1u8; 32])).await.unwrap();
369            store.put(10, FixedBytes::new([10u8; 32])).await.unwrap();
370            store.put(11, FixedBytes::new([11u8; 32])).await.unwrap();
371            store.put(14, FixedBytes::new([14u8; 32])).await.unwrap();
372
373            // Check gaps
374            let (current_end, start_next) = store.next_gap(0);
375            assert!(current_end.is_none());
376            assert_eq!(start_next, Some(1));
377
378            let (current_end, start_next) = store.next_gap(1);
379            assert_eq!(current_end, Some(1));
380            assert_eq!(start_next, Some(10));
381
382            let (current_end, start_next) = store.next_gap(10);
383            assert_eq!(current_end, Some(11));
384            assert_eq!(start_next, Some(14));
385
386            let (current_end, start_next) = store.next_gap(11);
387            assert_eq!(current_end, Some(11));
388            assert_eq!(start_next, Some(14));
389
390            let (current_end, start_next) = store.next_gap(12);
391            assert!(current_end.is_none());
392            assert_eq!(start_next, Some(14));
393
394            let (current_end, start_next) = store.next_gap(14);
395            assert_eq!(current_end, Some(14));
396            assert!(start_next.is_none());
397        });
398    }
399
400    #[test_traced]
401    fn test_missing_items() {
402        // Initialize the deterministic context
403        let executor = deterministic::Runner::default();
404        executor.start(|context| async move {
405            // Initialize the store
406            let cfg = Config {
407                partition: "test-ordinal".into(),
408                items_per_blob: NZU64!(DEFAULT_ITEMS_PER_BLOB),
409                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
410                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
411            };
412            let mut store =
413                Ordinal::<_, FixedBytes<32>>::init(context.child("storage"), cfg.clone(), None)
414                    .await
415                    .expect("Failed to initialize store");
416
417            // Test 1: Empty store - should return no items
418            assert_eq!(store.missing_items(0, 5), Vec::<u64>::new());
419            assert_eq!(store.missing_items(100, 10), Vec::<u64>::new());
420
421            // Test 2: Insert values with gaps
422            store.put(1, FixedBytes::new([1u8; 32])).await.unwrap();
423            store.put(2, FixedBytes::new([2u8; 32])).await.unwrap();
424            store.put(5, FixedBytes::new([5u8; 32])).await.unwrap();
425            store.put(6, FixedBytes::new([6u8; 32])).await.unwrap();
426            store.put(10, FixedBytes::new([10u8; 32])).await.unwrap();
427
428            // Test 3: Find missing items from the beginning
429            assert_eq!(store.missing_items(0, 5), vec![0, 3, 4, 7, 8]);
430            assert_eq!(store.missing_items(0, 6), vec![0, 3, 4, 7, 8, 9]);
431            assert_eq!(store.missing_items(0, 7), vec![0, 3, 4, 7, 8, 9]);
432
433            // Test 4: Find missing items from within a gap
434            assert_eq!(store.missing_items(3, 3), vec![3, 4, 7]);
435            assert_eq!(store.missing_items(4, 2), vec![4, 7]);
436
437            // Test 5: Find missing items from within a range
438            assert_eq!(store.missing_items(1, 3), vec![3, 4, 7]);
439            assert_eq!(store.missing_items(2, 4), vec![3, 4, 7, 8]);
440            assert_eq!(store.missing_items(5, 2), vec![7, 8]);
441
442            // Test 6: Find missing items after the last range (no more gaps)
443            assert_eq!(store.missing_items(11, 5), Vec::<u64>::new());
444            assert_eq!(store.missing_items(100, 10), Vec::<u64>::new());
445
446            // Test 7: Large gap scenario
447            store.put(1000, FixedBytes::new([100u8; 32])).await.unwrap();
448
449            // Gap between 10 and 1000
450            let items = store.missing_items(11, 10);
451            assert_eq!(items, vec![11, 12, 13, 14, 15, 16, 17, 18, 19, 20]);
452
453            // Request more items than available in gap
454            let items = store.missing_items(990, 15);
455            assert_eq!(
456                items,
457                vec![990, 991, 992, 993, 994, 995, 996, 997, 998, 999]
458            );
459
460            // Test 8: After syncing (data should remain consistent)
461            store.sync().await.unwrap();
462            assert_eq!(store.missing_items(0, 5), vec![0, 3, 4, 7, 8]);
463            assert_eq!(store.missing_items(3, 3), vec![3, 4, 7]);
464
465            // Test 9: Cross-blob boundary scenario
466            store.put(9999, FixedBytes::new([99u8; 32])).await.unwrap();
467            store
468                .put(10001, FixedBytes::new([101u8; 32]))
469                .await
470                .unwrap();
471
472            // Find missing items across blob boundary (10000 is the boundary)
473            let items = store.missing_items(9998, 5);
474            assert_eq!(items, vec![9998, 10000]);
475        });
476    }
477
478    #[test_traced]
479    fn test_restart() {
480        // Initialize the deterministic context
481        let executor = deterministic::Runner::default();
482        executor.start(|context| async move {
483            let cfg = Config {
484                partition: "test-ordinal".into(),
485                items_per_blob: NZU64!(DEFAULT_ITEMS_PER_BLOB),
486                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
487                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
488            };
489
490            // Insert data and close
491            {
492                let mut store =
493                    Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
494                        .await
495                        .expect("Failed to initialize store");
496
497                let values = vec![
498                    (0u64, FixedBytes::new([0u8; 32])),
499                    (100u64, FixedBytes::new([100u8; 32])),
500                    (1000u64, FixedBytes::new([200u8; 32])),
501                ];
502
503                for (index, value) in &values {
504                    store
505                        .put(*index, value.clone())
506                        .await
507                        .expect("Failed to put data");
508                }
509
510                store.sync().await.expect("Failed to sync store");
511            }
512
513            // Reopen with bits and verify committed data persisted
514            {
515                let mut bits0 = BitMap::zeroes(DEFAULT_ITEMS_PER_BLOB);
516                bits0.set(0, true);
517                bits0.set(100, true);
518                let mut bits1 = BitMap::zeroes(DEFAULT_ITEMS_PER_BLOB);
519                bits1.set(0, true);
520                let bits0 = Some(bits0);
521                let bits1 = Some(bits1);
522                let mut bits = BTreeMap::new();
523                bits.insert(0, &bits0);
524                bits.insert(1, &bits1);
525                let store = Ordinal::<_, FixedBytes<32>>::init(
526                    context.child("second"),
527                    cfg.clone(),
528                    Some(bits),
529                )
530                .await
531                .expect("Failed to initialize store");
532
533                let values = vec![
534                    (0u64, FixedBytes::new([0u8; 32])),
535                    (100u64, FixedBytes::new([100u8; 32])),
536                    (1000u64, FixedBytes::new([200u8; 32])),
537                ];
538
539                for (index, value) in &values {
540                    let retrieved = store
541                        .get(*index)
542                        .await
543                        .expect("Failed to get data")
544                        .expect("Data not found");
545                    assert_eq!(&retrieved, value);
546                }
547
548                // Check gaps are preserved
549                let (current_end, start_next) = store.next_gap(0);
550                assert_eq!(current_end, Some(0));
551                assert_eq!(start_next, Some(100));
552            }
553        });
554    }
555
556    #[test_traced]
557    fn test_invalid_record() {
558        // Initialize the deterministic context
559        let executor = deterministic::Runner::default();
560        executor.start(|context| async move {
561            let cfg = Config {
562                partition: "test-ordinal".into(),
563                items_per_blob: NZU64!(DEFAULT_ITEMS_PER_BLOB),
564                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
565                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
566            };
567
568            // Create store with data
569            {
570                let mut store =
571                    Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
572                        .await
573                        .expect("Failed to initialize store");
574
575                store
576                    .put(0, FixedBytes::new([42u8; 32]))
577                    .await
578                    .expect("Failed to put data");
579                store.sync().await.expect("Failed to sync store");
580            }
581
582            // Corrupt the data
583            {
584                let (blob, _) = context
585                    .open("test-ordinal", &0u64.to_be_bytes())
586                    .await
587                    .unwrap();
588                // Corrupt the CRC by changing a byte
589                blob.write_at_sync(32, vec![0xFF]).await.unwrap();
590            }
591
592            // Reopen without bits, deleting the stored corrupted data before replay.
593            {
594                let store =
595                    Ordinal::<_, FixedBytes<32>>::init(context.child("second"), cfg.clone(), None)
596                        .await
597                        .expect("Failed to initialize store");
598
599                let result = store.get(0).await.unwrap();
600                assert!(result.is_none());
601
602                assert!(!store.has(0));
603            }
604        });
605    }
606
607    #[test_traced]
608    fn test_get_nonexistent() {
609        // Initialize the deterministic context
610        let executor = deterministic::Runner::default();
611        executor.start(|context| async move {
612            // Initialize the store
613            let cfg = Config {
614                partition: "test-ordinal".into(),
615                items_per_blob: NZU64!(DEFAULT_ITEMS_PER_BLOB),
616                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
617                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
618            };
619            let store =
620                Ordinal::<_, FixedBytes<32>>::init(context.child("storage"), cfg.clone(), None)
621                    .await
622                    .expect("Failed to initialize store");
623
624            // Attempt to get an index that doesn't exist
625            let retrieved = store.get(999).await.expect("Failed to get data");
626            assert!(retrieved.is_none());
627
628            // Check has returns false
629            assert!(!store.has(999));
630        });
631    }
632
633    #[test_traced]
634    fn test_destroy() {
635        // Initialize the deterministic context
636        let executor = deterministic::Runner::default();
637        executor.start(|context| async move {
638            let cfg = Config {
639                partition: "test-ordinal".into(),
640                items_per_blob: NZU64!(DEFAULT_ITEMS_PER_BLOB),
641                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
642                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
643            };
644
645            // Create store with data
646            {
647                let mut store =
648                    Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
649                        .await
650                        .expect("Failed to initialize store");
651
652                store
653                    .put(0, FixedBytes::new([0u8; 32]))
654                    .await
655                    .expect("Failed to put data");
656                store
657                    .put(1000, FixedBytes::new([100u8; 32]))
658                    .await
659                    .expect("Failed to put data");
660
661                // Destroy the store
662                store.destroy().await.expect("Failed to destroy store");
663            }
664
665            // Try to create a new store - it should be empty
666            {
667                let store =
668                    Ordinal::<_, FixedBytes<32>>::init(context.child("second"), cfg.clone(), None)
669                        .await
670                        .expect("Failed to initialize store");
671
672                // Should not find any data
673                assert!(store.get(0).await.unwrap().is_none());
674                assert!(store.get(1000).await.unwrap().is_none());
675                assert!(!store.has(0));
676                assert!(!store.has(1000));
677            }
678        });
679    }
680
681    #[test_traced]
682    fn test_partial_record_write() {
683        // Initialize the deterministic context
684        let executor = deterministic::Runner::default();
685        executor.start(|context| async move {
686            let cfg = Config {
687                partition: "test-ordinal".into(),
688                items_per_blob: NZU64!(DEFAULT_ITEMS_PER_BLOB),
689                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
690                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
691            };
692
693            // Create store with data
694            {
695                let mut store =
696                    Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
697                        .await
698                        .expect("Failed to initialize store");
699
700                store
701                    .put(0, FixedBytes::new([42u8; 32]))
702                    .await
703                    .expect("Failed to put data");
704                store
705                    .put(1, FixedBytes::new([43u8; 32]))
706                    .await
707                    .expect("Failed to put data");
708                store.sync().await.expect("Failed to sync store");
709            }
710
711            // Corrupt by writing partial record (only value, no CRC)
712            {
713                let (blob, _) = context
714                    .open("test-ordinal", &0u64.to_be_bytes())
715                    .await
716                    .unwrap();
717                // Overwrite second record with partial data (32 bytes instead of 36)
718                blob.write_at_sync(36, vec![0xFF; 32]).await.unwrap();
719            }
720
721            // Reopen without bits and verify uncheckpointed data is deleted.
722            {
723                let store =
724                    Ordinal::<_, FixedBytes<32>>::init(context.child("second"), cfg.clone(), None)
725                        .await
726                        .expect("Failed to initialize store");
727
728                assert!(!store.has(0));
729                assert!(!store.has(1));
730
731                // Store should still be functional
732                let mut store_mut = store;
733                store_mut.put(1, FixedBytes::new([44u8; 32])).await.unwrap();
734                assert_eq!(
735                    store_mut.get(1).await.unwrap().unwrap(),
736                    FixedBytes::new([44u8; 32])
737                );
738            }
739        });
740    }
741
742    #[test_traced]
743    fn test_corrupted_value() {
744        // Initialize the deterministic context
745        let executor = deterministic::Runner::default();
746        executor.start(|context| async move {
747            let cfg = Config {
748                partition: "test-ordinal".into(),
749                items_per_blob: NZU64!(DEFAULT_ITEMS_PER_BLOB),
750                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
751                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
752            };
753
754            // Create store with data
755            {
756                let mut store =
757                    Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
758                        .await
759                        .expect("Failed to initialize store");
760
761                store
762                    .put(0, FixedBytes::new([42u8; 32]))
763                    .await
764                    .expect("Failed to put data");
765                store
766                    .put(1, FixedBytes::new([43u8; 32]))
767                    .await
768                    .expect("Failed to put data");
769                store.sync().await.expect("Failed to sync store");
770            }
771
772            // Corrupt the value portion of a record
773            {
774                let (blob, _) = context
775                    .open("test-ordinal", &0u64.to_be_bytes())
776                    .await
777                    .unwrap();
778                // Corrupt some bytes in the value of the first record
779                blob.write_at_sync(10, hex!("0xFFFFFFFF").to_vec())
780                    .await
781                    .unwrap();
782            }
783
784            // Reopen without bits and verify uncheckpointed data is deleted.
785            {
786                let store =
787                    Ordinal::<_, FixedBytes<32>>::init(context.child("second"), cfg.clone(), None)
788                        .await
789                        .expect("Failed to initialize store");
790
791                assert!(!store.has(0));
792                assert!(!store.has(1));
793            }
794        });
795    }
796
797    #[test_traced]
798    fn test_crc_corruptions() {
799        // Initialize the deterministic context
800        let executor = deterministic::Runner::default();
801        executor.start(|context| async move {
802            let cfg = Config {
803                partition: "test-ordinal".into(),
804                items_per_blob: NZU64!(10), // Small blob size for testing
805                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
806                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
807            };
808
809            // Create store with data across multiple blobs
810            {
811                let mut store =
812                    Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
813                        .await
814                        .expect("Failed to initialize store");
815
816                // Add values across 2 blobs
817                store.put(0, FixedBytes::new([0u8; 32])).await.unwrap();
818                store.put(5, FixedBytes::new([5u8; 32])).await.unwrap();
819                store.put(10, FixedBytes::new([10u8; 32])).await.unwrap();
820                store.put(15, FixedBytes::new([15u8; 32])).await.unwrap();
821                store.sync().await.expect("Failed to sync store");
822            }
823
824            // Corrupt CRCs in different blobs
825            {
826                // Corrupt CRC in first blob
827                let (blob, _) = context
828                    .open("test-ordinal", &0u64.to_be_bytes())
829                    .await
830                    .unwrap();
831                blob.write_at_sync(32, vec![0xFF]).await.unwrap(); // Corrupt CRC of index 0
832
833                // Corrupt value in second blob (which will invalidate CRC)
834                let (blob, _) = context
835                    .open("test-ordinal", &1u64.to_be_bytes())
836                    .await
837                    .unwrap();
838                blob.write_at_sync(5, vec![0xFF; 4]).await.unwrap(); // Corrupt value of index 10
839            }
840
841            // Reopen without bits and verify uncheckpointed data is deleted.
842            {
843                let store =
844                    Ordinal::<_, FixedBytes<32>>::init(context.child("second"), cfg.clone(), None)
845                        .await
846                        .expect("Failed to initialize store");
847
848                assert!(!store.has(0));
849                assert!(!store.has(5));
850                assert!(!store.has(10));
851                assert!(!store.has(15));
852            }
853        });
854    }
855
856    #[test_traced]
857    fn test_extra_bytes_in_blob() {
858        // Initialize the deterministic context
859        let executor = deterministic::Runner::default();
860        executor.start(|context| async move {
861            let cfg = Config {
862                partition: "test-ordinal".into(),
863                items_per_blob: NZU64!(DEFAULT_ITEMS_PER_BLOB),
864                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
865                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
866            };
867
868            // Create store with data
869            {
870                let mut store =
871                    Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
872                        .await
873                        .expect("Failed to initialize store");
874
875                store
876                    .put(0, FixedBytes::new([42u8; 32]))
877                    .await
878                    .expect("Failed to put data");
879                store
880                    .put(1, FixedBytes::new([43u8; 32]))
881                    .await
882                    .expect("Failed to put data");
883                store.sync().await.expect("Failed to sync store");
884            }
885
886            // Add extra bytes at the end of blob
887            {
888                let (blob, size) = context
889                    .open("test-ordinal", &0u64.to_be_bytes())
890                    .await
891                    .unwrap();
892                // Add garbage data that forms a complete but invalid record
893                // This avoids partial record issues
894                let mut garbage = vec![0xFF; 32]; // Invalid value
895                let invalid_crc = 0xDEADBEEFu32;
896                garbage.extend_from_slice(&invalid_crc.to_be_bytes());
897                assert_eq!(garbage.len(), 36); // Full record size
898                blob.write_at_sync(size, garbage).await.unwrap();
899            }
900
901            // Reopen without bits and verify uncheckpointed data is deleted.
902            {
903                let store =
904                    Ordinal::<_, FixedBytes<32>>::init(context.child("second"), cfg.clone(), None)
905                        .await
906                        .expect("Failed to initialize store");
907
908                assert!(!store.has(0));
909                assert!(!store.has(1));
910
911                // Store should still be functional
912                let mut store_mut = store;
913                store_mut.put(2, FixedBytes::new([44u8; 32])).await.unwrap();
914                assert_eq!(
915                    store_mut.get(2).await.unwrap().unwrap(),
916                    FixedBytes::new([44u8; 32])
917                );
918            }
919        });
920    }
921
922    #[test_traced]
923    fn test_zero_filled_records() {
924        // Initialize the deterministic context
925        let executor = deterministic::Runner::default();
926        executor.start(|context| async move {
927            let cfg = Config {
928                partition: "test-ordinal".into(),
929                items_per_blob: NZU64!(DEFAULT_ITEMS_PER_BLOB),
930                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
931                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
932            };
933
934            // Create blob with zero-filled space
935            {
936                let (blob, _) = context
937                    .open("test-ordinal", &0u64.to_be_bytes())
938                    .await
939                    .unwrap();
940
941                // Write zeros for several record positions
942                let zeros = vec![0u8; 36 * 5]; // 5 records worth of zeros
943                blob.write_at_sync(0, zeros).await.unwrap();
944
945                // Write a valid record after the zeros
946                let mut valid_record = vec![44u8; 32];
947                let crc = Crc32::checksum(&valid_record);
948                valid_record.extend_from_slice(&crc.to_be_bytes());
949                blob.write_at_sync(36 * 5, valid_record).await.unwrap();
950            }
951
952            // Initialize with bits and verify it handles zero-filled records
953            {
954                let mut section = BitMap::zeroes(DEFAULT_ITEMS_PER_BLOB);
955                section.set(5, true);
956                let section = Some(section);
957                let mut bits = BTreeMap::new();
958                bits.insert(0, &section);
959                let store = Ordinal::<_, FixedBytes<32>>::init(
960                    context.child("storage"),
961                    cfg.clone(),
962                    Some(bits),
963                )
964                .await
965                .expect("Failed to initialize store");
966
967                // Zero-filled positions should not be considered valid
968                for i in 0..5 {
969                    assert!(!store.has(i));
970                }
971
972                // The valid record should be found
973                assert!(store.has(5));
974                assert_eq!(
975                    store.get(5).await.unwrap().unwrap(),
976                    FixedBytes::new([44u8; 32])
977                );
978            }
979        });
980    }
981
982    fn test_operations_and_restart(num_values: usize) -> String {
983        // Initialize the deterministic context
984        let executor = deterministic::Runner::default();
985        executor.start(|mut context| async move {
986            let cfg = Config {
987                partition: "test-ordinal".into(),
988                items_per_blob: NZU64!(100), // Smaller blobs to test multiple blob handling
989                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
990                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
991            };
992
993            // Initialize the store
994            let mut store =
995                Ordinal::<_, FixedBytes<128>>::init(context.child("first"), cfg.clone(), None)
996                    .await
997                    .expect("Failed to initialize store");
998
999            // Generate and insert random values at various indices
1000            let mut values = Vec::new();
1001            let mut rng_index = 0u64;
1002
1003            for _ in 0..num_values {
1004                // Generate a pseudo-random index (sparse to test gaps)
1005                let mut index_bytes = [0u8; 8];
1006                context.fill_bytes(&mut index_bytes);
1007                let index_offset = u64::from_be_bytes(index_bytes) % 1000;
1008                let index = rng_index + index_offset;
1009                rng_index = index + 1;
1010
1011                // Generate random value
1012                let mut value = [0u8; 128];
1013                context.fill_bytes(&mut value);
1014                let value = FixedBytes::<128>::new(value);
1015
1016                store
1017                    .put(index, value.clone())
1018                    .await
1019                    .expect("Failed to put data");
1020                values.push((index, value));
1021            }
1022
1023            // Sync data
1024            store.sync().await.expect("Failed to sync");
1025
1026            // Verify all values can be retrieved
1027            for (index, value) in &values {
1028                let retrieved = store
1029                    .get(*index)
1030                    .await
1031                    .expect("Failed to get data")
1032                    .expect("Data not found");
1033                assert_eq!(&retrieved, value);
1034            }
1035
1036            // Test next_gap on various indices
1037            for i in 0..10 {
1038                let _ = store.next_gap(i * 100);
1039            }
1040
1041            // Sync and drop the store
1042            store.sync().await.expect("Failed to sync store");
1043            drop(store);
1044
1045            // Reopen the store
1046            let owned_bits = bits_for_indices(NZU64!(100), values.iter().map(|(index, _)| *index));
1047            let bits = owned_bits
1048                .iter()
1049                .map(|(section, bitmap)| (*section, bitmap))
1050                .collect();
1051            let mut store =
1052                Ordinal::<_, FixedBytes<128>>::init(context.child("second"), cfg, Some(bits))
1053                    .await
1054                    .expect("Failed to initialize store");
1055
1056            // Verify all values are still there after restart
1057            for (index, value) in &values {
1058                let retrieved = store
1059                    .get(*index)
1060                    .await
1061                    .expect("Failed to get data")
1062                    .expect("Data not found");
1063                assert_eq!(&retrieved, value);
1064            }
1065
1066            // Add more values after restart
1067            for _ in 0..10 {
1068                let mut index_bytes = [0u8; 8];
1069                context.fill_bytes(&mut index_bytes);
1070                let index = u64::from_be_bytes(index_bytes) % 10000;
1071
1072                let mut value = [0u8; 128];
1073                context.fill_bytes(&mut value);
1074                let value = FixedBytes::<128>::new(value);
1075
1076                store.put(index, value).await.expect("Failed to put data");
1077            }
1078
1079            // Final sync
1080            store.sync().await.expect("Failed to sync");
1081
1082            // Return the auditor state for comparison
1083            context.auditor().state()
1084        })
1085    }
1086
1087    #[test_group("slow")]
1088    #[test_traced]
1089    fn test_determinism() {
1090        let state1 = test_operations_and_restart(100);
1091        let state2 = test_operations_and_restart(100);
1092        assert_eq!(state1, state2);
1093    }
1094
1095    #[test_traced]
1096    fn test_prune_basic() {
1097        // Initialize the deterministic context
1098        let executor = deterministic::Runner::default();
1099        executor.start(|context| async move {
1100            let cfg = Config {
1101                partition: "test-ordinal".into(),
1102                items_per_blob: NZU64!(100), // Small blobs to test multiple blob handling
1103                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1104                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1105            };
1106
1107            let mut store =
1108                Ordinal::<_, FixedBytes<32>>::init(context.child("storage"), cfg.clone(), None)
1109                    .await
1110                    .expect("Failed to initialize store");
1111
1112            // Insert data across multiple blobs
1113            let values = vec![
1114                (0u64, FixedBytes::new([0u8; 32])),     // Blob 0
1115                (50u64, FixedBytes::new([50u8; 32])),   // Blob 0
1116                (100u64, FixedBytes::new([100u8; 32])), // Blob 1
1117                (150u64, FixedBytes::new([150u8; 32])), // Blob 1
1118                (200u64, FixedBytes::new([200u8; 32])), // Blob 2
1119                (300u64, FixedBytes::new([44u8; 32])),  // Blob 3
1120            ];
1121
1122            for (index, value) in &values {
1123                store
1124                    .put(*index, value.clone())
1125                    .await
1126                    .expect("Failed to put data");
1127            }
1128            store.sync().await.unwrap();
1129
1130            // Verify all values exist
1131            for (index, value) in &values {
1132                assert_eq!(store.get(*index).await.unwrap().unwrap(), *value);
1133            }
1134
1135            // Prune up to index 150 (should remove blob 0 only)
1136            store.prune(150).await.unwrap();
1137            let buffer = context.encode();
1138            assert!(buffer.contains("pruned_total 1"));
1139
1140            // Verify pruned data is gone
1141            assert!(!store.has(0));
1142            assert!(!store.has(50));
1143            assert!(store.get(0).await.unwrap().is_none());
1144            assert!(store.get(50).await.unwrap().is_none());
1145
1146            // Verify remaining data is still there
1147            assert!(store.has(100));
1148            assert!(store.has(150));
1149            assert!(store.has(200));
1150            assert!(store.has(300));
1151            assert_eq!(store.get(100).await.unwrap().unwrap(), values[2].1);
1152            assert_eq!(store.get(150).await.unwrap().unwrap(), values[3].1);
1153            assert_eq!(store.get(200).await.unwrap().unwrap(), values[4].1);
1154            assert_eq!(store.get(300).await.unwrap().unwrap(), values[5].1);
1155
1156            // Prune more aggressively - up to index 250 (should remove blob 1)
1157            store.prune(250).await.unwrap();
1158            let buffer = context.encode();
1159            assert!(buffer.contains("pruned_total 2"));
1160
1161            // Verify more data is pruned
1162            assert!(!store.has(100));
1163            assert!(!store.has(150));
1164            assert!(store.get(100).await.unwrap().is_none());
1165            assert!(store.get(150).await.unwrap().is_none());
1166
1167            // Verify remaining data
1168            assert!(store.has(200));
1169            assert!(store.has(300));
1170            assert_eq!(store.get(200).await.unwrap().unwrap(), values[4].1);
1171            assert_eq!(store.get(300).await.unwrap().unwrap(), values[5].1);
1172        });
1173    }
1174
1175    #[test_traced]
1176    fn test_prune_with_gaps() {
1177        // Initialize the deterministic context
1178        let executor = deterministic::Runner::default();
1179        executor.start(|context| async move {
1180            let cfg = Config {
1181                partition: "test-ordinal".into(),
1182                items_per_blob: NZU64!(100),
1183                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1184                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1185            };
1186
1187            let mut store =
1188                Ordinal::<_, FixedBytes<32>>::init(context.child("storage"), cfg.clone(), None)
1189                    .await
1190                    .expect("Failed to initialize store");
1191
1192            // Insert sparse data with gaps
1193            store.put(5, FixedBytes::new([5u8; 32])).await.unwrap();
1194            store.put(105, FixedBytes::new([105u8; 32])).await.unwrap();
1195            store.put(305, FixedBytes::new([49u8; 32])).await.unwrap();
1196            store.sync().await.unwrap();
1197
1198            // Check gaps before pruning
1199            let (current_end, next_start) = store.next_gap(0);
1200            assert!(current_end.is_none());
1201            assert_eq!(next_start, Some(5));
1202
1203            let (current_end, next_start) = store.next_gap(5);
1204            assert_eq!(current_end, Some(5));
1205            assert_eq!(next_start, Some(105));
1206
1207            // Prune up to index 150 (should remove blob 0)
1208            store.prune(150).await.unwrap();
1209
1210            // Verify pruned data is gone
1211            assert!(!store.has(5));
1212            assert!(store.get(5).await.unwrap().is_none());
1213
1214            // Verify remaining data and gaps
1215            assert!(store.has(105));
1216            assert!(store.has(305));
1217
1218            let (current_end, next_start) = store.next_gap(0);
1219            assert!(current_end.is_none());
1220            assert_eq!(next_start, Some(105));
1221
1222            let (current_end, next_start) = store.next_gap(105);
1223            assert_eq!(current_end, Some(105));
1224            assert_eq!(next_start, Some(305));
1225        });
1226    }
1227
1228    #[test_traced]
1229    fn test_prune_no_op() {
1230        // Initialize the deterministic context
1231        let executor = deterministic::Runner::default();
1232        executor.start(|context| async move {
1233            let cfg = Config {
1234                partition: "test-ordinal".into(),
1235                items_per_blob: NZU64!(100),
1236                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1237                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1238            };
1239
1240            let mut store =
1241                Ordinal::<_, FixedBytes<32>>::init(context.child("storage"), cfg.clone(), None)
1242                    .await
1243                    .expect("Failed to initialize store");
1244
1245            // Insert data
1246            store.put(100, FixedBytes::new([100u8; 32])).await.unwrap();
1247            store.put(200, FixedBytes::new([200u8; 32])).await.unwrap();
1248            store.sync().await.unwrap();
1249
1250            // Try to prune before any data - should be no-op
1251            store.prune(50).await.unwrap();
1252
1253            // Verify no data was actually pruned
1254            assert!(store.has(100));
1255            assert!(store.has(200));
1256            let buffer = context.encode();
1257            assert!(buffer.contains("pruned_total 0"));
1258
1259            // Try to prune exactly at blob boundary - should be no-op
1260            store.prune(100).await.unwrap();
1261
1262            // Verify still no data pruned
1263            assert!(store.has(100));
1264            assert!(store.has(200));
1265            let buffer = context.encode();
1266            assert!(buffer.contains("pruned_total 0"));
1267        });
1268    }
1269
1270    #[test_traced]
1271    fn test_prune_empty_store() {
1272        // Initialize the deterministic context
1273        let executor = deterministic::Runner::default();
1274        executor.start(|context| async move {
1275            let cfg = Config {
1276                partition: "test-ordinal".into(),
1277                items_per_blob: NZU64!(100),
1278                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1279                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1280            };
1281
1282            let mut store =
1283                Ordinal::<_, FixedBytes<32>>::init(context.child("storage"), cfg.clone(), None)
1284                    .await
1285                    .expect("Failed to initialize store");
1286
1287            // Try to prune empty store
1288            store.prune(1000).await.unwrap();
1289
1290            // Store should still be functional
1291            store.put(0, FixedBytes::new([0u8; 32])).await.unwrap();
1292            assert!(store.has(0));
1293        });
1294    }
1295
1296    #[test_traced]
1297    fn test_prune_after_restart() {
1298        // Initialize the deterministic context
1299        let executor = deterministic::Runner::default();
1300        executor.start(|context| async move {
1301            let cfg = Config {
1302                partition: "test-ordinal".into(),
1303                items_per_blob: NZU64!(100),
1304                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1305                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1306            };
1307
1308            // Create store and add data
1309            {
1310                let mut store =
1311                    Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
1312                        .await
1313                        .expect("Failed to initialize store");
1314
1315                store.put(0, FixedBytes::new([0u8; 32])).await.unwrap();
1316                store.put(100, FixedBytes::new([100u8; 32])).await.unwrap();
1317                store.put(200, FixedBytes::new([200u8; 32])).await.unwrap();
1318                store.sync().await.unwrap();
1319            }
1320
1321            // Reopen with bits and prune
1322            {
1323                let mut bits0 = BitMap::zeroes(100);
1324                bits0.set(0, true);
1325                let mut bits1 = BitMap::zeroes(100);
1326                bits1.set(0, true);
1327                let mut bits2 = BitMap::zeroes(100);
1328                bits2.set(0, true);
1329                let bits0 = Some(bits0);
1330                let bits1 = Some(bits1);
1331                let bits2 = Some(bits2);
1332                let mut bits = BTreeMap::new();
1333                bits.insert(0, &bits0);
1334                bits.insert(1, &bits1);
1335                bits.insert(2, &bits2);
1336                let mut store = Ordinal::<_, FixedBytes<32>>::init(
1337                    context.child("second"),
1338                    cfg.clone(),
1339                    Some(bits),
1340                )
1341                .await
1342                .expect("Failed to initialize store");
1343
1344                // Verify data is there
1345                assert!(store.has(0));
1346                assert!(store.has(100));
1347                assert!(store.has(200));
1348
1349                // Prune up to index 150
1350                store.prune(150).await.unwrap();
1351
1352                // Verify pruning worked
1353                assert!(!store.has(0));
1354                assert!(store.has(100));
1355                assert!(store.has(200));
1356
1357                store.sync().await.unwrap();
1358            }
1359
1360            // Reopen again and verify pruning persisted
1361            {
1362                let mut bits1 = BitMap::zeroes(100);
1363                bits1.set(0, true);
1364                let mut bits2 = BitMap::zeroes(100);
1365                bits2.set(0, true);
1366                let bits1 = Some(bits1);
1367                let bits2 = Some(bits2);
1368                let mut bits = BTreeMap::new();
1369                bits.insert(1, &bits1);
1370                bits.insert(2, &bits2);
1371                let store = Ordinal::<_, FixedBytes<32>>::init(
1372                    context.child("third"),
1373                    cfg.clone(),
1374                    Some(bits),
1375                )
1376                .await
1377                .expect("Failed to initialize store");
1378
1379                assert!(!store.has(0));
1380                assert!(store.has(100));
1381                assert!(store.has(200));
1382
1383                // Check gaps
1384                let (current_end, next_start) = store.next_gap(0);
1385                assert!(current_end.is_none());
1386                assert_eq!(next_start, Some(100));
1387            }
1388        });
1389    }
1390
1391    #[test_traced]
1392    fn test_prune_multiple_operations() {
1393        // Initialize the deterministic context
1394        let executor = deterministic::Runner::default();
1395        executor.start(|context| async move {
1396            let cfg = Config {
1397                partition: "test-ordinal".into(),
1398                items_per_blob: NZU64!(50), // Smaller blobs for more granular testing
1399                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1400                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1401            };
1402
1403            let mut store =
1404                Ordinal::<_, FixedBytes<32>>::init(context.child("storage"), cfg.clone(), None)
1405                    .await
1406                    .expect("Failed to initialize store");
1407
1408            // Insert data across many blobs
1409            let mut values = Vec::new();
1410            for i in 0..10 {
1411                let index = i * 50 + 25; // Middle of each blob
1412                let value = FixedBytes::new([i as u8; 32]);
1413                store.put(index, value.clone()).await.unwrap();
1414                values.push((index, value));
1415            }
1416            store.sync().await.unwrap();
1417
1418            // Prune incrementally
1419            for i in 1..5 {
1420                let prune_index = i * 50 + 10;
1421                store.prune(prune_index).await.unwrap();
1422
1423                // Verify appropriate data is pruned
1424                for (index, _) in &values {
1425                    if *index < prune_index {
1426                        assert!(!store.has(*index), "Index {index} should be pruned");
1427                    } else {
1428                        assert!(store.has(*index), "Index {index} should not be pruned");
1429                    }
1430                }
1431            }
1432
1433            // Check final state
1434            let buffer = context.encode();
1435            assert!(buffer.contains("pruned_total 4"));
1436
1437            // Verify remaining data
1438            for i in 4..10 {
1439                let index = i * 50 + 25;
1440                assert!(store.has(index));
1441                assert_eq!(
1442                    store.get(index).await.unwrap().unwrap(),
1443                    values[i as usize].1
1444                );
1445            }
1446        });
1447    }
1448
1449    #[test_traced]
1450    fn test_prune_blob_boundaries() {
1451        // Initialize the deterministic context
1452        let executor = deterministic::Runner::default();
1453        executor.start(|context| async move {
1454            let cfg = Config {
1455                partition: "test-ordinal".into(),
1456                items_per_blob: NZU64!(100),
1457                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1458                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1459            };
1460
1461            let mut store =
1462                Ordinal::<_, FixedBytes<32>>::init(context.child("storage"), cfg.clone(), None)
1463                    .await
1464                    .expect("Failed to initialize store");
1465
1466            // Insert data at blob boundaries
1467            store.put(0, FixedBytes::new([0u8; 32])).await.unwrap(); // Start of blob 0
1468            store.put(99, FixedBytes::new([99u8; 32])).await.unwrap(); // End of blob 0
1469            store.put(100, FixedBytes::new([100u8; 32])).await.unwrap(); // Start of blob 1
1470            store.put(199, FixedBytes::new([199u8; 32])).await.unwrap(); // End of blob 1
1471            store.put(200, FixedBytes::new([200u8; 32])).await.unwrap(); // Start of blob 2
1472            store.sync().await.unwrap();
1473
1474            // Test various pruning points around boundaries
1475
1476            // Prune exactly at blob boundary (100) - should prune blob 0
1477            store.prune(100).await.unwrap();
1478            assert!(!store.has(0));
1479            assert!(!store.has(99));
1480            assert!(store.has(100));
1481            assert!(store.has(199));
1482            assert!(store.has(200));
1483
1484            // Prune just before next boundary (199) - should not prune blob 1
1485            store.prune(199).await.unwrap();
1486            assert!(store.has(100));
1487            assert!(store.has(199));
1488            assert!(store.has(200));
1489
1490            // Prune exactly at next boundary (200) - should prune blob 1
1491            store.prune(200).await.unwrap();
1492            assert!(!store.has(100));
1493            assert!(!store.has(199));
1494            assert!(store.has(200));
1495
1496            let buffer = context.encode();
1497            assert!(buffer.contains("pruned_total 2"));
1498        });
1499    }
1500
1501    #[test_traced]
1502    fn test_prune_non_contiguous_sections() {
1503        // Initialize the deterministic context
1504        let executor = deterministic::Runner::default();
1505        executor.start(|context| async move {
1506            let cfg = Config {
1507                partition: "test-ordinal".into(),
1508                items_per_blob: NZU64!(100),
1509                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1510                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1511            };
1512
1513            let mut store =
1514                Ordinal::<_, FixedBytes<32>>::init(context.child("storage"), cfg.clone(), None)
1515                    .await
1516                    .expect("Failed to initialize store");
1517
1518            // Insert data in non-contiguous sections (0, 2, 5, 7)
1519            store.put(0, FixedBytes::new([0u8; 32])).await.unwrap(); // Section 0
1520            store.put(250, FixedBytes::new([50u8; 32])).await.unwrap(); // Section 2 (250/100 = 2)
1521            store.put(500, FixedBytes::new([44u8; 32])).await.unwrap(); // Section 5 (500/100 = 5)
1522            store.put(750, FixedBytes::new([45u8; 32])).await.unwrap(); // Section 7 (750/100 = 7)
1523            store.sync().await.unwrap();
1524
1525            // Verify all data exists initially
1526            assert!(store.has(0));
1527            assert!(store.has(250));
1528            assert!(store.has(500));
1529            assert!(store.has(750));
1530
1531            // Prune up to section 3 (index 300) - should remove sections 0 and 2
1532            store.prune(300).await.unwrap();
1533
1534            // Verify correct data was pruned
1535            assert!(!store.has(0)); // Section 0 pruned
1536            assert!(!store.has(250)); // Section 2 pruned
1537            assert!(store.has(500)); // Section 5 remains
1538            assert!(store.has(750)); // Section 7 remains
1539
1540            let buffer = context.encode();
1541            assert!(buffer.contains("pruned_total 2"));
1542
1543            // Prune up to section 6 (index 600) - should remove section 5
1544            store.prune(600).await.unwrap();
1545
1546            // Verify section 5 was pruned
1547            assert!(!store.has(500)); // Section 5 pruned
1548            assert!(store.has(750)); // Section 7 remains
1549
1550            let buffer = context.encode();
1551            assert!(buffer.contains("pruned_total 3"));
1552
1553            // Prune everything - should remove section 7
1554            store.prune(1000).await.unwrap();
1555
1556            // Verify all data is gone
1557            assert!(!store.has(750)); // Section 7 pruned
1558
1559            let buffer = context.encode();
1560            assert!(buffer.contains("pruned_total 4"));
1561        });
1562    }
1563
1564    #[test_traced]
1565    fn test_prune_removes_correct_pending() {
1566        // Initialize the deterministic context
1567        let executor = deterministic::Runner::default();
1568        executor.start(|context| async move {
1569            let cfg = Config {
1570                partition: "test-ordinal".into(),
1571                items_per_blob: NZU64!(100),
1572                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1573                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1574            };
1575            let mut store =
1576                Ordinal::<_, FixedBytes<32>>::init(context.child("storage"), cfg.clone(), None)
1577                    .await
1578                    .expect("Failed to initialize store");
1579
1580            // Insert and sync some data in blob 0
1581            store.put(5, FixedBytes::new([5u8; 32])).await.unwrap();
1582            store.sync().await.unwrap();
1583
1584            // Add pending entries to blob 0 and blob 1
1585            store.put(10, FixedBytes::new([10u8; 32])).await.unwrap(); // blob 0
1586            store.put(110, FixedBytes::new([110u8; 32])).await.unwrap(); // blob 1
1587
1588            // Verify all data is visible before pruning
1589            assert!(store.has(5));
1590            assert!(store.has(10));
1591            assert!(store.has(110));
1592
1593            // Prune up to index 100, which should remove blob 0 (indices 0-99).
1594            store.prune(150).await.unwrap();
1595
1596            // Verify that synced and pending entries in blob 0 are removed.
1597            assert!(!store.has(5));
1598            assert!(!store.has(10));
1599
1600            // Verify that the pending entry in blob 1 remains.
1601            assert!(store.has(110));
1602            assert_eq!(
1603                store.get(110).await.unwrap().unwrap(),
1604                FixedBytes::new([110u8; 32])
1605            );
1606
1607            // Sync the remaining pending entry and verify it's still there.
1608            store.sync().await.unwrap();
1609            assert!(store.has(110));
1610            assert_eq!(
1611                store.get(110).await.unwrap().unwrap(),
1612                FixedBytes::new([110u8; 32])
1613            );
1614        });
1615    }
1616
1617    #[test_traced]
1618    fn test_init_without_bits_deletes_existing_data() {
1619        // Initialize the deterministic context
1620        let executor = deterministic::Runner::default();
1621        executor.start(|context| async move {
1622            let cfg = Config {
1623                partition: "test-ordinal".into(),
1624                items_per_blob: NZU64!(10), // Small blob size for testing
1625                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1626                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1627            };
1628
1629            // Create store with data across multiple sections
1630            {
1631                let mut store =
1632                    Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
1633                        .await
1634                        .expect("Failed to initialize store");
1635
1636                // Section 0 (indices 0-9)
1637                store.put(0, FixedBytes::new([0u8; 32])).await.unwrap();
1638                store.put(5, FixedBytes::new([5u8; 32])).await.unwrap();
1639                store.put(9, FixedBytes::new([9u8; 32])).await.unwrap();
1640
1641                // Section 1 (indices 10-19)
1642                store.put(10, FixedBytes::new([10u8; 32])).await.unwrap();
1643                store.put(15, FixedBytes::new([15u8; 32])).await.unwrap();
1644
1645                // Section 2 (indices 20-29)
1646                store.put(25, FixedBytes::new([25u8; 32])).await.unwrap();
1647
1648                store.sync().await.unwrap();
1649            }
1650
1651            // Reinitialize with bits = None, deleting uncheckpointed data.
1652            {
1653                let store =
1654                    Ordinal::<_, FixedBytes<32>>::init(context.child("second"), cfg.clone(), None)
1655                        .await
1656                        .expect("Failed to initialize store");
1657
1658                assert!(!store.has(0));
1659                assert!(!store.has(5));
1660                assert!(!store.has(9));
1661                assert!(!store.has(10));
1662                assert!(!store.has(15));
1663                assert!(!store.has(25));
1664                assert!(!store.has(1));
1665                assert!(!store.has(11));
1666                assert!(!store.has(20));
1667            }
1668        });
1669    }
1670
1671    #[test_traced]
1672    fn test_init_empty_hashmap() {
1673        // Initialize the deterministic context
1674        let executor = deterministic::Runner::default();
1675        executor.start(|context| async move {
1676            let cfg = Config {
1677                partition: "test-ordinal".into(),
1678                items_per_blob: NZU64!(10),
1679                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1680                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1681            };
1682
1683            // Create store with data
1684            {
1685                let mut store =
1686                    Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
1687                        .await
1688                        .expect("Failed to initialize store");
1689
1690                store.put(0, FixedBytes::new([0u8; 32])).await.unwrap();
1691                store.put(10, FixedBytes::new([10u8; 32])).await.unwrap();
1692                store.put(20, FixedBytes::new([20u8; 32])).await.unwrap();
1693
1694                store.sync().await.unwrap();
1695            }
1696
1697            // Reinitialize with an empty map, deleting every stored section.
1698            {
1699                let bits: BTreeMap<u64, &Option<BitMap>> = BTreeMap::new();
1700                let store = Ordinal::<_, FixedBytes<32>>::init(
1701                    context.child("second"),
1702                    cfg.clone(),
1703                    Some(bits),
1704                )
1705                .await
1706                .expect("Failed to initialize store with bits");
1707
1708                // No records should be available since no sections were in the bits map
1709                assert!(!store.has(0));
1710                assert!(!store.has(10));
1711                assert!(!store.has(20));
1712            }
1713
1714            // The explicit empty map deletes the uncheckpointed blobs.
1715            {
1716                let mut section = BitMap::zeroes(10);
1717                section.set(0, true);
1718                let section = Some(section);
1719                let mut bits = BTreeMap::new();
1720                bits.insert(0, &section);
1721                let result = Ordinal::<_, FixedBytes<32>>::init(
1722                    context.child("third"),
1723                    cfg.clone(),
1724                    Some(bits),
1725                )
1726                .await;
1727                assert!(matches!(result, Err(Error::MissingRecord(0))));
1728            }
1729        });
1730    }
1731
1732    #[test_traced]
1733    fn test_init_selective_sections() {
1734        // Initialize the deterministic context
1735        let executor = deterministic::Runner::default();
1736        executor.start(|context| async move {
1737            let cfg = Config {
1738                partition: "test-ordinal".into(),
1739                items_per_blob: NZU64!(10),
1740                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1741                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1742            };
1743
1744            // Create store with data in multiple sections
1745            {
1746                let mut store =
1747                    Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
1748                        .await
1749                        .expect("Failed to initialize store");
1750
1751                // Section 0 (indices 0-9)
1752                for i in 0..10 {
1753                    store.put(i, FixedBytes::new([i as u8; 32])).await.unwrap();
1754                }
1755
1756                // Section 1 (indices 10-19)
1757                for i in 10..20 {
1758                    store.put(i, FixedBytes::new([i as u8; 32])).await.unwrap();
1759                }
1760
1761                // Section 2 (indices 20-29)
1762                for i in 20..30 {
1763                    store.put(i, FixedBytes::new([i as u8; 32])).await.unwrap();
1764                }
1765
1766                store.sync().await.unwrap();
1767            }
1768
1769            // Reinitialize with bits for only section 1
1770            {
1771                let mut bits_map: BTreeMap<u64, &Option<BitMap>> = BTreeMap::new();
1772
1773                // Create a BitMap that marks indices 12, 15, and 18 as present
1774                let mut bitmap = BitMap::zeroes(10);
1775                bitmap.set(2, true); // Index 12 (offset 2 in section 1)
1776                bitmap.set(5, true); // Index 15 (offset 5 in section 1)
1777                bitmap.set(8, true); // Index 18 (offset 8 in section 1)
1778                let bitmap_option = Some(bitmap);
1779
1780                bits_map.insert(1, &bitmap_option);
1781
1782                let store = Ordinal::<_, FixedBytes<32>>::init(
1783                    context.child("second"),
1784                    cfg.clone(),
1785                    Some(bits_map),
1786                )
1787                .await
1788                .expect("Failed to initialize store with bits");
1789
1790                // Only specified indices from section 1 should be available
1791                assert!(store.has(12));
1792                assert!(store.has(15));
1793                assert!(store.has(18));
1794
1795                // Other indices from section 1 should not be available
1796                assert!(!store.has(10));
1797                assert!(!store.has(11));
1798                assert!(!store.has(13));
1799                assert!(!store.has(14));
1800                assert!(!store.has(16));
1801                assert!(!store.has(17));
1802                assert!(!store.has(19));
1803
1804                // All indices from sections 0 and 2 should not be available
1805                for i in 0..10 {
1806                    assert!(!store.has(i));
1807                }
1808                for i in 20..30 {
1809                    assert!(!store.has(i));
1810                }
1811
1812                // Verify the available values
1813                assert_eq!(
1814                    store.get(12).await.unwrap().unwrap(),
1815                    FixedBytes::new([12u8; 32])
1816                );
1817                assert_eq!(
1818                    store.get(15).await.unwrap().unwrap(),
1819                    FixedBytes::new([15u8; 32])
1820                );
1821                assert_eq!(
1822                    store.get(18).await.unwrap().unwrap(),
1823                    FixedBytes::new([18u8; 32])
1824                );
1825            }
1826
1827            // Unselected records in a retained section are physically cleared.
1828            {
1829                let mut bitmap = BitMap::zeroes(10);
1830                bitmap.set(0, true); // Index 10
1831                let bitmap_option = Some(bitmap);
1832                let mut bits_map: BTreeMap<u64, &Option<BitMap>> = BTreeMap::new();
1833                bits_map.insert(1, &bitmap_option);
1834                let result = Ordinal::<_, FixedBytes<32>>::init(
1835                    context.child("third"),
1836                    cfg.clone(),
1837                    Some(bits_map),
1838                )
1839                .await;
1840                assert!(matches!(result, Err(Error::MissingRecord(10))));
1841            }
1842        });
1843    }
1844
1845    #[test_traced]
1846    fn test_init_none_option_all_records_exist() {
1847        // Initialize the deterministic context
1848        let executor = deterministic::Runner::default();
1849        executor.start(|context| async move {
1850            let cfg = Config {
1851                partition: "test-ordinal".into(),
1852                items_per_blob: NZU64!(5),
1853                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1854                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1855            };
1856
1857            // Create store with all records in a section
1858            {
1859                let mut store =
1860                    Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
1861                        .await
1862                        .expect("Failed to initialize store");
1863
1864                // Fill section 1 completely (indices 5-9)
1865                for i in 5..10 {
1866                    store.put(i, FixedBytes::new([i as u8; 32])).await.unwrap();
1867                }
1868
1869                store.sync().await.unwrap();
1870            }
1871
1872            // Reinitialize with None option for section 1 (expects all records)
1873            {
1874                let mut bits_map: BTreeMap<u64, &Option<BitMap>> = BTreeMap::new();
1875                let none_option: Option<BitMap> = None;
1876                bits_map.insert(1, &none_option);
1877
1878                let store = Ordinal::<_, FixedBytes<32>>::init(
1879                    context.child("second"),
1880                    cfg.clone(),
1881                    Some(bits_map),
1882                )
1883                .await
1884                .expect("Failed to initialize store with bits");
1885
1886                // All records in section 1 should be available
1887                for i in 5..10 {
1888                    assert!(store.has(i));
1889                    assert_eq!(
1890                        store.get(i).await.unwrap().unwrap(),
1891                        FixedBytes::new([i as u8; 32])
1892                    );
1893                }
1894            }
1895        });
1896    }
1897
1898    #[test_traced]
1899    #[should_panic(expected = "Failed to initialize store with bits: MissingRecord(6)")]
1900    fn test_init_none_option_missing_record_panics() {
1901        // Initialize the deterministic context
1902        let executor = deterministic::Runner::default();
1903        executor.start(|context| async move {
1904            let cfg = Config {
1905                partition: "test-ordinal".into(),
1906                items_per_blob: NZU64!(5),
1907                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1908                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1909            };
1910
1911            // Create store with missing record in a section
1912            {
1913                let mut store =
1914                    Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
1915                        .await
1916                        .expect("Failed to initialize store");
1917
1918                // Fill section 1 partially (skip index 6)
1919                store.put(5, FixedBytes::new([5u8; 32])).await.unwrap();
1920                // Skip index 6
1921                store.put(7, FixedBytes::new([7u8; 32])).await.unwrap();
1922                store.put(8, FixedBytes::new([8u8; 32])).await.unwrap();
1923                store.put(9, FixedBytes::new([9u8; 32])).await.unwrap();
1924
1925                store.sync().await.unwrap();
1926            }
1927
1928            // Reinitialize with None option for section 1 (expects all records)
1929            // This should panic because index 6 is missing
1930            {
1931                let mut bits_map: BTreeMap<u64, &Option<BitMap>> = BTreeMap::new();
1932                let none_option: Option<BitMap> = None;
1933                bits_map.insert(1, &none_option);
1934
1935                let _store = Ordinal::<_, FixedBytes<32>>::init(
1936                    context.child("second"),
1937                    cfg.clone(),
1938                    Some(bits_map),
1939                )
1940                .await
1941                .expect("Failed to initialize store with bits");
1942            }
1943        });
1944    }
1945
1946    #[test_traced]
1947    fn test_init_mixed_sections() {
1948        // Initialize the deterministic context
1949        let executor = deterministic::Runner::default();
1950        executor.start(|context| async move {
1951            let cfg = Config {
1952                partition: "test-ordinal".into(),
1953                items_per_blob: NZU64!(5),
1954                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
1955                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
1956            };
1957
1958            // Create store with data in multiple sections
1959            {
1960                let mut store =
1961                    Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
1962                        .await
1963                        .expect("Failed to initialize store");
1964
1965                // Section 0: indices 0-4 (fill completely)
1966                for i in 0..5 {
1967                    store.put(i, FixedBytes::new([i as u8; 32])).await.unwrap();
1968                }
1969
1970                // Section 1: indices 5-9 (fill partially)
1971                store.put(5, FixedBytes::new([5u8; 32])).await.unwrap();
1972                store.put(7, FixedBytes::new([7u8; 32])).await.unwrap();
1973                store.put(9, FixedBytes::new([9u8; 32])).await.unwrap();
1974
1975                // Section 2: indices 10-14 (fill completely)
1976                for i in 10..15 {
1977                    store.put(i, FixedBytes::new([i as u8; 32])).await.unwrap();
1978                }
1979
1980                store.sync().await.unwrap();
1981            }
1982
1983            // Reinitialize with mixed bits configuration
1984            {
1985                let mut bits_map: BTreeMap<u64, &Option<BitMap>> = BTreeMap::new();
1986
1987                // Section 0: None option (expects all records)
1988                let none_option: Option<BitMap> = None;
1989                bits_map.insert(0, &none_option);
1990
1991                // Section 1: BitMap with specific indices
1992                let mut bitmap1 = BitMap::zeroes(5);
1993                bitmap1.set(0, true); // Index 5
1994                bitmap1.set(2, true); // Index 7
1995                                      // Note: not setting bit for index 9, so it should be ignored
1996                let bitmap1_option = Some(bitmap1);
1997                bits_map.insert(1, &bitmap1_option);
1998
1999                // Section 2: Not in map, so it should be removed entirely.
2000
2001                let store = Ordinal::<_, FixedBytes<32>>::init(
2002                    context.child("second"),
2003                    cfg.clone(),
2004                    Some(bits_map),
2005                )
2006                .await
2007                .expect("Failed to initialize store with bits");
2008
2009                // All records from section 0 should be available
2010                for i in 0..5 {
2011                    assert!(store.has(i));
2012                    assert_eq!(
2013                        store.get(i).await.unwrap().unwrap(),
2014                        FixedBytes::new([i as u8; 32])
2015                    );
2016                }
2017
2018                // Only specified records from section 1 should be available
2019                assert!(store.has(5));
2020                assert!(store.has(7));
2021                assert!(!store.has(6));
2022                assert!(!store.has(8));
2023                assert!(!store.has(9)); // Not set in bitmap
2024
2025                // No records from section 2 should be available
2026                for i in 10..15 {
2027                    assert!(!store.has(i));
2028                }
2029            }
2030        });
2031    }
2032
2033    #[test_traced]
2034    #[should_panic(expected = "Failed to initialize store with bits: MissingRecord(2)")]
2035    fn test_init_corrupted_records() {
2036        // Initialize the deterministic context
2037        let executor = deterministic::Runner::default();
2038        executor.start(|context| async move {
2039            let cfg = Config {
2040                partition: "test-ordinal".into(),
2041                items_per_blob: NZU64!(5),
2042                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
2043                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
2044            };
2045
2046            // Create store with data and corrupt one record
2047            {
2048                let mut store =
2049                    Ordinal::<_, FixedBytes<32>>::init(context.child("first"), cfg.clone(), None)
2050                        .await
2051                        .expect("Failed to initialize store");
2052
2053                // Section 0: indices 0-4
2054                for i in 0..5 {
2055                    store.put(i, FixedBytes::new([i as u8; 32])).await.unwrap();
2056                }
2057
2058                store.sync().await.unwrap();
2059            }
2060
2061            // Corrupt record at index 2
2062            {
2063                let (blob, _) = context
2064                    .open("test-ordinal", &0u64.to_be_bytes())
2065                    .await
2066                    .unwrap();
2067                // Corrupt the CRC of record at index 2
2068                let offset = 2 * 36 + 32; // 2 * record_size + value_size
2069                blob.write_at_sync(offset, vec![0xFF]).await.unwrap();
2070            }
2071
2072            // Reinitialize with bits that include the corrupted record
2073            {
2074                let mut bits_map: BTreeMap<u64, &Option<BitMap>> = BTreeMap::new();
2075
2076                // Create a BitMap that includes the corrupted record
2077                let mut bitmap = BitMap::zeroes(5);
2078                bitmap.set(0, true); // Index 0
2079                bitmap.set(2, true); // Index 2 (corrupted) - this will cause a panic
2080                bitmap.set(4, true); // Index 4
2081                let bitmap_option = Some(bitmap);
2082                bits_map.insert(0, &bitmap_option);
2083
2084                let _store = Ordinal::<_, FixedBytes<32>>::init(
2085                    context.child("second"),
2086                    cfg.clone(),
2087                    Some(bits_map),
2088                )
2089                .await
2090                .expect("Failed to initialize store with bits");
2091            }
2092        });
2093    }
2094
2095    /// A dummy value that will fail parsing if the value is 0.
2096    #[derive(Debug, PartialEq, Eq)]
2097    pub struct DummyValue {
2098        pub value: u64,
2099    }
2100
2101    impl Write for DummyValue {
2102        fn write(&self, buf: &mut impl BufMut) {
2103            self.value.write(buf);
2104        }
2105    }
2106
2107    impl Read for DummyValue {
2108        type Cfg = ();
2109
2110        fn read_cfg(buf: &mut impl Buf, _: &Self::Cfg) -> Result<Self, commonware_codec::Error> {
2111            let value = u64::read(buf)?;
2112            if value == 0 {
2113                return Err(commonware_codec::Error::Invalid(
2114                    "DummyValue",
2115                    "value must be non-zero",
2116                ));
2117            }
2118            Ok(Self { value })
2119        }
2120    }
2121
2122    impl FixedSize for DummyValue {
2123        const SIZE: usize = u64::SIZE;
2124    }
2125
2126    #[test_traced]
2127    fn test_init_skip_unparseable_record() {
2128        // Initialize the deterministic context
2129        let executor = deterministic::Runner::default();
2130        executor.start(|context| async move {
2131            let cfg = Config {
2132                partition: "test-ordinal".into(),
2133                items_per_blob: NZU64!(1),
2134                write_buffer: NZUsize!(DEFAULT_WRITE_BUFFER),
2135                replay_buffer: NZUsize!(DEFAULT_REPLAY_BUFFER),
2136            };
2137
2138            // Create store with records, including one that will fail to parse if recovered.
2139            {
2140                let mut store =
2141                    Ordinal::<_, DummyValue>::init(context.child("first"), cfg.clone(), None)
2142                        .await
2143                        .expect("Failed to initialize store");
2144
2145                // Add records at indices 1, 2, 4
2146                store.put(1, DummyValue { value: 1 }).await.unwrap();
2147                store.put(2, DummyValue { value: 0 }).await.unwrap(); // will fail parsing
2148                store.put(4, DummyValue { value: 4 }).await.unwrap();
2149
2150                store.sync().await.unwrap();
2151            }
2152
2153            // Reinitialize without bits and verify uncheckpointed data is deleted.
2154            {
2155                let store =
2156                    Ordinal::<_, DummyValue>::init(context.child("second"), cfg.clone(), None)
2157                        .await
2158                        .expect("Failed to initialize store");
2159
2160                assert!(!store.has(1));
2161                assert!(!store.has(2));
2162                assert!(!store.has(4));
2163            }
2164        });
2165    }
2166}