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