Skip to main content

commonware_storage/archive/
mod.rs

1//! A write-once key-value store for ordered data.
2//!
3//! [Archive] is a key-value store designed for workloads where data is written only once and each
4//! item is addressed by both an `index` and a `key`. Workloads with unique indices should use [Archive]
5//! and workloads with overlapping indices should use [MultiArchive] (allows all items with the same index
6//! to be retrieved). The same key may be stored at multiple indices in either case, and a key lookup may
7//! return any of the associated values.
8//!
9//! Storage errors from mutable operations are considered fatal for the current handle and may
10//! leave its in-memory state inconsistent with the underlying storage.
11
12use commonware_codec::Codec;
13use commonware_runtime::Handle;
14use commonware_utils::Array;
15use std::future::Future;
16use thiserror::Error;
17
18pub mod immutable;
19pub mod prunable;
20
21#[cfg(all(test, feature = "arbitrary"))]
22mod conformance;
23
24/// Subject of a `get` or `has` operation.
25pub enum Identifier<'a, K: Array> {
26    Index(u64),
27    Key(&'a K),
28}
29
30/// Errors that can occur when interacting with the archive.
31#[derive(Debug, Error)]
32pub enum Error {
33    #[error("journal error: {0}")]
34    Journal(#[from] crate::journal::Error),
35    #[error("ordinal error: {0}")]
36    Ordinal(#[from] crate::ordinal::Error),
37    #[error("metadata error: {0}")]
38    Metadata(#[from] crate::metadata::Error),
39    #[error("freezer error: {0}")]
40    Freezer(#[from] crate::freezer::Error),
41    #[error("record corrupted")]
42    RecordCorrupted,
43    #[error("already pruned to: {0}")]
44    AlreadyPrunedTo(u64),
45    #[error("record too large")]
46    RecordTooLarge,
47}
48
49/// A write-once key-value store addressed by both an index and a key.
50pub trait Archive: Send {
51    /// The type of the key.
52    type Key: Array;
53
54    /// The type of the value.
55    type Value: Codec + Send;
56
57    /// Store an item in [Archive].
58    ///
59    /// Indices are unique: if the index already exists, put does nothing and returns. Duplicate
60    /// indices can be stored via [MultiArchive::put_multi]. Keys need not be unique: the same key
61    /// may be stored at multiple indices, and a subsequent [Archive::get] or [Archive::has] call
62    /// with an [Identifier::Key] identifier may return any of the values associated with that key.
63    fn put(
64        &mut self,
65        index: u64,
66        key: Self::Key,
67        value: Self::Value,
68    ) -> impl Future<Output = Result<(), Error>> + Send;
69
70    /// Perform a [Archive::put] and [Archive::sync] in a single operation.
71    fn put_sync(
72        &mut self,
73        index: u64,
74        key: Self::Key,
75        value: Self::Value,
76    ) -> impl Future<Output = Result<(), Error>> + Send {
77        async move {
78            self.put(index, key, value).await?;
79            self.sync().await
80        }
81    }
82
83    /// Perform a [Archive::put] and [Archive::start_sync] in a single operation.
84    ///
85    /// If the index already exists (making the put a no-op), the returned handle still reports
86    /// the durability of all previously accepted writes, including the original write for this
87    /// index if its sync is still in flight.
88    fn put_start_sync(
89        &mut self,
90        index: u64,
91        key: Self::Key,
92        value: Self::Value,
93    ) -> impl Future<Output = Result<Handle<()>, Error>> + Send {
94        async move {
95            self.put(index, key, value).await?;
96            self.start_sync().await
97        }
98    }
99
100    /// Retrieve an item from [Archive].
101    ///
102    /// Note that if the [Archive] is a [MultiArchive], there may be multiple values associated with the
103    /// same [Identifier::Index]. If there are multiple values, the first stored will be returned. Use
104    /// [MultiArchive::get_all] to retrieve all values at an index.
105    fn get<'a>(
106        &'a self,
107        identifier: Identifier<'a, Self::Key>,
108    ) -> impl Future<Output = Result<Option<Self::Value>, Error>> + Send + use<'a, Self>;
109
110    /// Check if an item exists in [Archive].
111    fn has<'a>(
112        &'a self,
113        identifier: Identifier<'a, Self::Key>,
114    ) -> impl Future<Output = Result<bool, Error>> + Send + use<'a, Self>;
115
116    /// Retrieve the end of the current range including `index` (inclusive) and
117    /// the start of the next range after `index` (if it exists).
118    ///
119    /// This is useful for driving backfill operations over the archive.
120    fn next_gap(&self, index: u64) -> (Option<u64>, Option<u64>);
121
122    /// Returns up to `max` missing items starting from `start`.
123    ///
124    /// This method iterates through gaps between existing ranges, collecting missing indices
125    /// until either `max` items are found or there are no more gaps to fill.
126    fn missing_items(&self, index: u64, max: usize) -> Vec<u64>;
127
128    /// Retrieve an iterator over all populated ranges (inclusive) within the [Archive].
129    fn ranges(&self) -> impl Iterator<Item = (u64, u64)>;
130
131    /// Retrieve an iterator over ranges that overlap or follow `from`.
132    fn ranges_from(&self, from: u64) -> impl Iterator<Item = (u64, u64)>;
133
134    /// Retrieve the first index in the [Archive].
135    fn first_index(&self) -> Option<u64>;
136
137    /// Retrieve the last index in the [Archive].
138    fn last_index(&self) -> Option<u64>;
139
140    /// Sync all pending writes.
141    fn sync(&mut self) -> impl Future<Output = Result<(), Error>> + Send;
142
143    /// Request that all pending writes are synced.
144    ///
145    /// The returned handle completes once every write accepted before this call is durable,
146    /// including writes covered by a sync that is still in flight. Implementations without a
147    /// non-blocking sync path may complete the sync before returning an already-finished handle.
148    fn start_sync(&mut self) -> impl Future<Output = Result<Handle<()>, Error>> + Send {
149        async move {
150            self.sync().await?;
151            Ok(Handle::ready(Ok(())))
152        }
153    }
154
155    /// Remove all persistent data created by this [Archive].
156    fn destroy(self) -> impl Future<Output = Result<(), Error>> + Send;
157}
158
159/// Extension of [Archive] that supports multiple items at the same index.
160///
161/// Unlike [Archive::put], which is a no-op when the index already exists,
162/// [MultiArchive::put_multi] allows storing additional `(key, value)` pairs
163/// at an existing index.
164pub trait MultiArchive: Archive {
165    /// Retrieve all values stored at the given index.
166    ///
167    /// Returns `None` if the index does not exist or has been pruned.
168    fn get_all(
169        &self,
170        index: u64,
171    ) -> impl Future<Output = Result<Option<Vec<Self::Value>>, Error>> + Send + use<'_, Self>;
172
173    /// Check whether `key` is stored at `index`.
174    ///
175    /// Unlike [Archive::has] with [Identifier::Key], the check is scoped to a
176    /// single index. Unlike [MultiArchive::get_all], no values are fetched.
177    fn has_at<'a>(
178        &'a self,
179        index: u64,
180        key: &'a Self::Key,
181    ) -> impl Future<Output = Result<bool, Error>> + Send + use<'a, Self>;
182
183    /// Store an item, allowing multiple items at the same index.
184    ///
185    /// Multiple items may share the same `index`. If the same key is stored at
186    /// multiple indices, any associated value may be returned when queried with
187    /// [Identifier::Key].
188    fn put_multi(
189        &mut self,
190        index: u64,
191        key: Self::Key,
192        value: Self::Value,
193    ) -> impl Future<Output = Result<(), Error>> + Send;
194
195    /// Perform a [MultiArchive::put_multi] and [Archive::sync] in a single operation.
196    fn put_multi_sync(
197        &mut self,
198        index: u64,
199        key: Self::Key,
200        value: Self::Value,
201    ) -> impl Future<Output = Result<(), Error>> + Send {
202        async move {
203            self.put_multi(index, key, value).await?;
204            self.sync().await
205        }
206    }
207
208    /// Perform a [MultiArchive::put_multi] and [Archive::start_sync] in a single operation.
209    fn put_multi_start_sync(
210        &mut self,
211        index: u64,
212        key: Self::Key,
213        value: Self::Value,
214    ) -> impl Future<Output = Result<Handle<()>, Error>> + Send {
215        async move {
216            self.put_multi(index, key, value).await?;
217            self.start_sync().await
218        }
219    }
220}
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225    use crate::translator::TwoCap;
226    use commonware_codec::DecodeExt;
227    use commonware_macros::{test_group, test_traced};
228    use commonware_runtime::{
229        buffer::paged::CacheRef,
230        deterministic::{self, Context},
231        telemetry::metrics::has_metric_value,
232        Metrics as _, Runner, Supervisor as _,
233    };
234    use commonware_utils::{sequence::FixedBytes, NZUsize, NZU16, NZU64};
235    use rand::RngExt as _;
236    use std::{
237        collections::BTreeMap,
238        num::{NonZeroU16, NonZeroUsize},
239    };
240
241    fn test_key(key: &str) -> FixedBytes<64> {
242        let mut buf = [0u8; 64];
243        let key = key.as_bytes();
244        assert!(key.len() <= buf.len());
245        buf[..key.len()].copy_from_slice(key);
246        FixedBytes::decode(buf.as_ref()).unwrap()
247    }
248
249    const PAGE_SIZE: NonZeroU16 = NZU16!(1024);
250    const PAGE_CACHE_SIZE: NonZeroUsize = NZUsize!(10);
251
252    async fn create_prunable(
253        context: Context,
254        compression: Option<u8>,
255    ) -> impl MultiArchive<Key = FixedBytes<64>, Value = i32> {
256        let cfg = prunable::Config {
257            translator: TwoCap,
258            key_partition: "test-key".into(),
259            key_page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
260            value_partition: "test-value".into(),
261            compression,
262            codec_config: (),
263            items_per_section: NZU64!(1024),
264            key_write_buffer: NZUsize!(1024),
265            value_write_buffer: NZUsize!(1024),
266            replay_buffer: NZUsize!(1024),
267        };
268        prunable::Archive::init(context, cfg).await.unwrap()
269    }
270
271    async fn create_immutable(
272        context: Context,
273        compression: Option<u8>,
274    ) -> impl Archive<Key = FixedBytes<64>, Value = i32> {
275        let cfg = immutable::Config {
276            metadata_partition: "test-metadata".into(),
277            freezer_table_partition: "test-freezer-table".into(),
278            freezer_table_initial_size: 64,
279            freezer_table_resize_frequency: 2,
280            freezer_table_resize_chunk_size: 32,
281            freezer_key_partition: "test-freezer-key".into(),
282            freezer_key_page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
283            freezer_value_partition: "test-freezer-value".into(),
284            freezer_value_target_size: 1024 * 1024,
285            freezer_value_compression: compression,
286            ordinal_partition: "test-ordinal".into(),
287            items_per_section: NZU64!(1024),
288            freezer_key_write_buffer: NZUsize!(1024 * 1024),
289            freezer_value_write_buffer: NZUsize!(1024 * 1024),
290            ordinal_write_buffer: NZUsize!(1024 * 1024),
291            replay_buffer: NZUsize!(1024 * 1024),
292            codec_config: (),
293        };
294        immutable::Archive::init(context, cfg).await.unwrap()
295    }
296
297    async fn test_put_get_impl(mut archive: impl Archive<Key = FixedBytes<64>, Value = i32>) {
298        let index = 1u64;
299        let key = test_key("testkey");
300        let data = 1;
301
302        // Has the key before put
303        let has = archive
304            .has(Identifier::Index(index))
305            .await
306            .expect("Failed to check key");
307        assert!(!has);
308        let has = archive
309            .has(Identifier::Key(&key))
310            .await
311            .expect("Failed to check key");
312        assert!(!has);
313
314        // Put the key-data pair
315        archive
316            .put(index, key.clone(), data)
317            .await
318            .expect("Failed to put data");
319
320        // Has the key after put
321        let has = archive
322            .has(Identifier::Index(index))
323            .await
324            .expect("Failed to check key");
325        assert!(has);
326        let has = archive
327            .has(Identifier::Key(&key))
328            .await
329            .expect("Failed to check key");
330        assert!(has);
331
332        // Get the data by key
333        let retrieved = archive
334            .get(Identifier::Key(&key))
335            .await
336            .expect("Failed to get data");
337        assert_eq!(retrieved, Some(data));
338
339        // Get the data by index
340        let retrieved = archive
341            .get(Identifier::Index(index))
342            .await
343            .expect("Failed to get data");
344        assert_eq!(retrieved, Some(data));
345
346        // Force a sync
347        archive.sync().await.expect("Failed to sync data");
348    }
349
350    #[test_traced]
351    fn test_put_get_prunable_no_compression() {
352        let executor = deterministic::Runner::default();
353        executor.start(|context| async move {
354            let archive = create_prunable(context, None).await;
355            test_put_get_impl(archive).await;
356        });
357    }
358
359    #[test_traced]
360    fn test_put_get_prunable_compression() {
361        let executor = deterministic::Runner::default();
362        executor.start(|context| async move {
363            let archive = create_prunable(context, Some(3)).await;
364            test_put_get_impl(archive).await;
365        });
366    }
367
368    #[test_traced]
369    fn test_put_get_immutable_no_compression() {
370        let executor = deterministic::Runner::default();
371        executor.start(|context| async move {
372            let archive = create_immutable(context, None).await;
373            test_put_get_impl(archive).await;
374        });
375    }
376
377    #[test_traced]
378    fn test_put_get_immutable_compression() {
379        let executor = deterministic::Runner::default();
380        executor.start(|context| async move {
381            let archive = create_immutable(context, Some(3)).await;
382            test_put_get_impl(archive).await;
383        });
384    }
385
386    async fn test_duplicate_key_impl(mut archive: impl Archive<Key = FixedBytes<64>, Value = i32>) {
387        let index = 1u64;
388        let key = test_key("duplicate");
389        let data1 = 1;
390        let data2 = 2;
391
392        // Put the key-data pair
393        archive
394            .put(index, key.clone(), data1)
395            .await
396            .expect("Failed to put data");
397
398        // Put the key-data pair again (should be idempotent)
399        archive
400            .put(index, key.clone(), data2)
401            .await
402            .expect("Duplicate put should not fail");
403
404        // Get the data back - should still be the first value
405        let retrieved = archive
406            .get(Identifier::Index(index))
407            .await
408            .expect("Failed to get data")
409            .expect("Data not found");
410        assert_eq!(retrieved, data1);
411
412        let retrieved = archive
413            .get(Identifier::Key(&key))
414            .await
415            .expect("Failed to get data")
416            .expect("Data not found");
417        assert_eq!(retrieved, data1);
418    }
419
420    #[test_traced]
421    fn test_duplicate_key_prunable_no_compression() {
422        let executor = deterministic::Runner::default();
423        executor.start(|context| async move {
424            let archive = create_prunable(context, None).await;
425            test_duplicate_key_impl(archive).await;
426        });
427    }
428
429    #[test_traced]
430    fn test_duplicate_key_prunable_compression() {
431        let executor = deterministic::Runner::default();
432        executor.start(|context| async move {
433            let archive = create_prunable(context, Some(3)).await;
434            test_duplicate_key_impl(archive).await;
435        });
436    }
437
438    #[test_traced]
439    fn test_duplicate_key_immutable_no_compression() {
440        let executor = deterministic::Runner::default();
441        executor.start(|context| async move {
442            let archive = create_immutable(context, None).await;
443            test_duplicate_key_impl(archive).await;
444        });
445    }
446
447    async fn test_duplicate_key_cross_index_impl(
448        mut archive: impl Archive<Key = FixedBytes<64>, Value = i32>,
449    ) {
450        // Store the same key at two different indices; distinct values only so
451        // the test can observe which entry wins a key lookup.
452        let key = test_key("dupe-xindex");
453        archive.put(2, key.clone(), 20).await.expect("put(2)");
454        archive.put(5, key.clone(), 50).await.expect("put(5)");
455
456        // Both indices must resolve individually.
457        assert_eq!(
458            archive.get(Identifier::Index(2)).await.unwrap(),
459            Some(20),
460            "Index(2) must resolve to the value stored at 2"
461        );
462        assert_eq!(
463            archive.get(Identifier::Index(5)).await.unwrap(),
464            Some(50),
465            "Index(5) must resolve to the value stored at 5"
466        );
467
468        // Key lookup may return either value per the contract; just assert it
469        // returns one of them and that `has` reports presence.
470        let got = archive
471            .get(Identifier::Key(&key))
472            .await
473            .unwrap()
474            .expect("key lookup must find at least one entry");
475        assert!(got == 20 || got == 50, "unexpected value: {got}");
476        assert!(archive.has(Identifier::Key(&key)).await.unwrap());
477    }
478
479    #[test_traced]
480    fn test_duplicate_key_cross_index_prunable_no_compression() {
481        let executor = deterministic::Runner::default();
482        executor.start(|context| async move {
483            let archive = create_prunable(context, None).await;
484            test_duplicate_key_cross_index_impl(archive).await;
485        });
486    }
487
488    #[test_traced]
489    fn test_duplicate_key_cross_index_prunable_compression() {
490        let executor = deterministic::Runner::default();
491        executor.start(|context| async move {
492            let archive = create_prunable(context, Some(3)).await;
493            test_duplicate_key_cross_index_impl(archive).await;
494        });
495    }
496
497    #[test_traced]
498    fn test_duplicate_key_cross_index_immutable_no_compression() {
499        let executor = deterministic::Runner::default();
500        executor.start(|context| async move {
501            let archive = create_immutable(context, None).await;
502            test_duplicate_key_cross_index_impl(archive).await;
503        });
504    }
505
506    #[test_traced]
507    fn test_duplicate_key_cross_index_immutable_compression() {
508        let executor = deterministic::Runner::default();
509        executor.start(|context| async move {
510            let archive = create_immutable(context, Some(3)).await;
511            test_duplicate_key_cross_index_impl(archive).await;
512        });
513    }
514
515    #[test_traced]
516    fn test_duplicate_key_immutable_compression() {
517        let executor = deterministic::Runner::default();
518        executor.start(|context| async move {
519            let archive = create_immutable(context, Some(3)).await;
520            test_duplicate_key_impl(archive).await;
521        });
522    }
523
524    async fn test_get_nonexistent_impl(archive: impl Archive<Key = FixedBytes<64>, Value = i32>) {
525        // Attempt to get an index that doesn't exist
526        let index = 1u64;
527        let retrieved: Option<i32> = archive
528            .get(Identifier::Index(index))
529            .await
530            .expect("Failed to get data");
531        assert!(retrieved.is_none());
532
533        // Attempt to get a key that doesn't exist
534        let key = test_key("nonexistent");
535        let retrieved = archive
536            .get(Identifier::Key(&key))
537            .await
538            .expect("Failed to get data");
539        assert!(retrieved.is_none());
540    }
541
542    #[test_traced]
543    fn test_get_nonexistent_prunable_no_compression() {
544        let executor = deterministic::Runner::default();
545        executor.start(|context| async move {
546            let archive = create_prunable(context, None).await;
547            test_get_nonexistent_impl(archive).await;
548        });
549    }
550
551    #[test_traced]
552    fn test_get_nonexistent_prunable_compression() {
553        let executor = deterministic::Runner::default();
554        executor.start(|context| async move {
555            let archive = create_prunable(context, Some(3)).await;
556            test_get_nonexistent_impl(archive).await;
557        });
558    }
559
560    #[test_traced]
561    fn test_get_nonexistent_immutable_no_compression() {
562        let executor = deterministic::Runner::default();
563        executor.start(|context| async move {
564            let archive = create_immutable(context, None).await;
565            test_get_nonexistent_impl(archive).await;
566        });
567    }
568
569    #[test_traced]
570    fn test_get_nonexistent_immutable_compression() {
571        let executor = deterministic::Runner::default();
572        executor.start(|context| async move {
573            let archive = create_immutable(context, Some(3)).await;
574            test_get_nonexistent_impl(archive).await;
575        });
576    }
577
578    async fn test_persistence_impl<A, F, Fut>(context: Context, creator: F, compression: Option<u8>)
579    where
580        A: Archive<Key = FixedBytes<64>, Value = i32>,
581        F: Fn(Context, Option<u8>) -> Fut,
582        Fut: Future<Output = A>,
583    {
584        // Create and populate archive
585        {
586            let mut archive = creator(context.child("first"), compression).await;
587
588            // Insert multiple keys
589            let keys = vec![
590                (1u64, test_key("key1"), 1),
591                (2u64, test_key("key2"), 2),
592                (3u64, test_key("key3"), 3),
593            ];
594
595            for (index, key, data) in &keys {
596                archive
597                    .put(*index, key.clone(), *data)
598                    .await
599                    .expect("Failed to put data");
600            }
601
602            // Sync and drop the archive
603            archive.sync().await.expect("Failed to sync archive");
604        }
605
606        // Reopen and verify data
607        {
608            let archive = creator(context.child("second"), compression).await;
609
610            // Verify all keys are still present
611            let keys = vec![
612                (1u64, test_key("key1"), 1),
613                (2u64, test_key("key2"), 2),
614                (3u64, test_key("key3"), 3),
615            ];
616
617            for (index, key, expected_data) in &keys {
618                let retrieved = archive
619                    .get(Identifier::Index(*index))
620                    .await
621                    .expect("Failed to get data")
622                    .expect("Data not found");
623                assert_eq!(retrieved, *expected_data);
624
625                let retrieved = archive
626                    .get(Identifier::Key(key))
627                    .await
628                    .expect("Failed to get data")
629                    .expect("Data not found");
630                assert_eq!(retrieved, *expected_data);
631            }
632        }
633    }
634
635    #[test_traced]
636    fn test_persistence_prunable_no_compression() {
637        let executor = deterministic::Runner::default();
638        executor.start(|context| async move {
639            test_persistence_impl(context, create_prunable, None).await;
640        });
641    }
642
643    #[test_traced]
644    fn test_persistence_prunable_compression() {
645        let executor = deterministic::Runner::default();
646        executor.start(|context| async move {
647            test_persistence_impl(context, create_prunable, Some(3)).await;
648        });
649    }
650
651    #[test_traced]
652    fn test_persistence_immutable_no_compression() {
653        let executor = deterministic::Runner::default();
654        executor.start(|context| async move {
655            test_persistence_impl(context, create_immutable, None).await;
656        });
657    }
658
659    #[test_traced]
660    fn test_persistence_immutable_compression() {
661        let executor = deterministic::Runner::default();
662        executor.start(|context| async move {
663            test_persistence_impl(context, create_immutable, Some(3)).await;
664        });
665    }
666
667    async fn test_ranges_impl<A, F, Fut>(mut context: Context, creator: F, compression: Option<u8>)
668    where
669        A: Archive<Key = FixedBytes<64>, Value = i32>,
670        F: Fn(Context, Option<u8>) -> Fut,
671        Fut: Future<Output = A>,
672    {
673        let mut keys = BTreeMap::new();
674        {
675            let mut archive = creator(context.child("first"), compression).await;
676
677            // Insert 100 keys with gaps
678            let mut last_index = 0u64;
679            while keys.len() < 100 {
680                let gap: u64 = context.random_range(1..=10);
681                let index = last_index + gap;
682                last_index = index;
683
684                let mut key_bytes = [0u8; 64];
685                context.fill(&mut key_bytes);
686                let key = FixedBytes::<64>::decode(key_bytes.as_ref()).unwrap();
687                let data: i32 = context.random();
688
689                if keys.contains_key(&index) {
690                    continue;
691                }
692                keys.insert(index, (key.clone(), data));
693
694                archive
695                    .put(index, key, data)
696                    .await
697                    .expect("Failed to put data");
698            }
699
700            archive.sync().await.expect("Failed to sync archive");
701        }
702
703        {
704            let archive = creator(context.child("second"), compression).await;
705            let sorted_indices: Vec<u64> = keys.keys().cloned().collect();
706
707            // Check gap before the first element
708            let (current_end, start_next) = archive.next_gap(0);
709            assert!(current_end.is_none());
710            assert_eq!(start_next, Some(sorted_indices[0]));
711
712            // Check gaps between elements
713            let mut i = 0;
714            while i < sorted_indices.len() {
715                let current_index = sorted_indices[i];
716
717                // Find the end of the current contiguous block
718                let mut j = i;
719                while j + 1 < sorted_indices.len() && sorted_indices[j + 1] == sorted_indices[j] + 1
720                {
721                    j += 1;
722                }
723                let block_end_index = sorted_indices[j];
724                let next_actual_index = if j + 1 < sorted_indices.len() {
725                    Some(sorted_indices[j + 1])
726                } else {
727                    None
728                };
729
730                let (current_end, start_next) = archive.next_gap(current_index);
731                assert_eq!(current_end, Some(block_end_index));
732                assert_eq!(start_next, next_actual_index);
733
734                // If there's a gap, check an index within the gap
735                if let Some(next_index) = next_actual_index {
736                    if next_index > block_end_index + 1 {
737                        let in_gap_index = block_end_index + 1;
738                        let (current_end, start_next) = archive.next_gap(in_gap_index);
739                        assert!(current_end.is_none());
740                        assert_eq!(start_next, Some(next_index));
741                    }
742                }
743                i = j + 1;
744            }
745
746            // Check the last element
747            let last_index = *sorted_indices.last().unwrap();
748            let (current_end, start_next) = archive.next_gap(last_index);
749            assert!(current_end.is_some());
750            assert!(start_next.is_none());
751        }
752    }
753
754    #[test_traced]
755    fn test_ranges_prunable_no_compression() {
756        let executor = deterministic::Runner::default();
757        executor.start(|context| async move {
758            test_ranges_impl(context, create_prunable, None).await;
759        });
760    }
761
762    #[test_traced]
763    fn test_ranges_prunable_compression() {
764        let executor = deterministic::Runner::default();
765        executor.start(|context| async move {
766            test_ranges_impl(context, create_prunable, Some(3)).await;
767        });
768    }
769
770    #[test_traced]
771    fn test_ranges_immutable_no_compression() {
772        let executor = deterministic::Runner::default();
773        executor.start(|context| async move {
774            test_ranges_impl(context, create_immutable, None).await;
775        });
776    }
777
778    #[test_traced]
779    fn test_ranges_immutable_compression() {
780        let executor = deterministic::Runner::default();
781        executor.start(|context| async move {
782            test_ranges_impl(context, create_immutable, Some(3)).await;
783        });
784    }
785
786    async fn test_many_keys_impl<A, F, Fut>(
787        mut context: Context,
788        creator: F,
789        compression: Option<u8>,
790        num: usize,
791    ) where
792        A: Archive<Key = FixedBytes<64>, Value = i32>,
793        F: Fn(Context, Option<u8>) -> Fut,
794        Fut: Future<Output = A>,
795    {
796        // Insert many keys
797        let mut keys = BTreeMap::new();
798        {
799            let mut archive = creator(context.child("first"), compression).await;
800            while keys.len() < num {
801                let index = keys.len() as u64;
802                let mut key = [0u8; 64];
803                context.fill(&mut key);
804                let key = FixedBytes::<64>::decode(key.as_ref()).unwrap();
805                let data: i32 = context.random();
806
807                archive
808                    .put(index, key.clone(), data)
809                    .await
810                    .expect("Failed to put data");
811                keys.insert(key, (index, data));
812
813                // Randomly sync the archive
814                if context.random_bool(0.1) {
815                    archive.sync().await.expect("Failed to sync archive");
816                }
817            }
818            archive.sync().await.expect("Failed to sync archive");
819
820            // Ensure all keys can be retrieved
821            for (key, (index, data)) in &keys {
822                let retrieved = archive
823                    .get(Identifier::Index(*index))
824                    .await
825                    .expect("Failed to get data")
826                    .expect("Data not found");
827                assert_eq!(&retrieved, data);
828                let retrieved = archive
829                    .get(Identifier::Key(key))
830                    .await
831                    .expect("Failed to get data")
832                    .expect("Data not found");
833                assert_eq!(&retrieved, data);
834            }
835        }
836
837        // Reinitialize and verify
838        {
839            let archive = creator(context.child("second"), compression).await;
840
841            // Ensure all keys can be retrieved
842            for (key, (index, data)) in &keys {
843                let retrieved = archive
844                    .get(Identifier::Index(*index))
845                    .await
846                    .expect("Failed to get data")
847                    .expect("Data not found");
848                assert_eq!(&retrieved, data);
849                let retrieved = archive
850                    .get(Identifier::Key(key))
851                    .await
852                    .expect("Failed to get data")
853                    .expect("Data not found");
854                assert_eq!(&retrieved, data);
855            }
856        }
857    }
858
859    fn test_many_keys_determinism<F, Fut, A>(creator: F, compression: Option<u8>, num: usize)
860    where
861        A: Archive<Key = FixedBytes<64>, Value = i32>,
862        F: Fn(Context, Option<u8>) -> Fut + Copy + Send + 'static,
863        Fut: Future<Output = A> + Send,
864    {
865        let executor = deterministic::Runner::default();
866        let state1 = executor.start(|context| async move {
867            test_many_keys_impl(context.child("storage"), creator, compression, num).await;
868            context.auditor().state()
869        });
870        let executor = deterministic::Runner::default();
871        let state2 = executor.start(|context| async move {
872            test_many_keys_impl(context.child("storage"), creator, compression, num).await;
873            context.auditor().state()
874        });
875        assert_eq!(state1, state2);
876    }
877
878    #[test_traced]
879    fn test_many_keys_prunable_no_compression() {
880        test_many_keys_determinism(create_prunable, None, 1_000);
881    }
882
883    #[test_traced]
884    fn test_many_keys_prunable_compression() {
885        test_many_keys_determinism(create_prunable, Some(3), 1_000);
886    }
887
888    #[test_traced]
889    fn test_many_keys_immutable_no_compression() {
890        test_many_keys_determinism(create_immutable, None, 1_000);
891    }
892
893    #[test_traced]
894    fn test_many_keys_immutable_compression() {
895        test_many_keys_determinism(create_immutable, Some(3), 1_000);
896    }
897
898    #[test_group("slow")]
899    #[test_traced]
900    fn test_many_keys_prunable_large() {
901        test_many_keys_determinism(create_prunable, None, 50_000);
902    }
903
904    #[test_group("slow")]
905    #[test_traced]
906    fn test_many_keys_immutable_large() {
907        test_many_keys_determinism(create_immutable, None, 50_000);
908    }
909
910    async fn test_put_multi_and_get_impl(
911        context: Context,
912        mut archive: impl MultiArchive<Key = FixedBytes<64>, Value = i32>,
913    ) {
914        // Put three items at the same index with different keys
915        let index = 5u64;
916        let key_a = test_key("aaa");
917        let key_b = test_key("bbb");
918        let key_c = test_key("ccc");
919
920        archive
921            .put_multi(index, key_a.clone(), 10)
922            .await
923            .expect("put_multi a");
924        archive
925            .put_multi(index, key_b.clone(), 20)
926            .await
927            .expect("put_multi b");
928        archive
929            .put_multi(index, key_c.clone(), 30)
930            .await
931            .expect("put_multi c");
932
933        // Retrieve each by key
934        assert_eq!(
935            archive.get(Identifier::Key(&key_a)).await.unwrap(),
936            Some(10)
937        );
938        assert_eq!(
939            archive.get(Identifier::Key(&key_b)).await.unwrap(),
940            Some(20)
941        );
942        assert_eq!(
943            archive.get(Identifier::Key(&key_c)).await.unwrap(),
944            Some(30)
945        );
946
947        // Missing key returns None
948        let missing = test_key("zzz");
949        assert_eq!(archive.get(Identifier::Key(&missing)).await.unwrap(), None);
950
951        // items_tracked reflects unique indices, not total items
952        let buffer = context.encode();
953        assert!(has_metric_value(&buffer, "items_tracked", 1));
954    }
955
956    #[test_traced]
957    fn test_put_multi_and_get_prunable() {
958        let executor = deterministic::Runner::default();
959        executor.start(|context| async move {
960            let archive = create_prunable(context.child("storage"), None).await;
961            test_put_multi_and_get_impl(context, archive).await;
962        });
963    }
964
965    async fn test_put_multi_duplicate_key_impl(
966        context: Context,
967        mut archive: impl MultiArchive<Key = FixedBytes<64>, Value = i32>,
968    ) {
969        let key = test_key("dup");
970        archive.put_multi(5, key.clone(), 10).await.unwrap();
971        archive.put_multi(7, key.clone(), 20).await.unwrap();
972
973        // Duplicate key is allowed across indices.
974        assert_eq!(archive.get(Identifier::Index(5)).await.unwrap(), Some(10));
975        assert_eq!(archive.get(Identifier::Index(7)).await.unwrap(), Some(20));
976        assert_eq!(archive.get_all(5).await.unwrap(), Some(vec![10]));
977        assert_eq!(archive.get_all(7).await.unwrap(), Some(vec![20]));
978
979        // Like Archive::put, duplicate keys may return any associated value.
980        assert!(matches!(
981            archive.get(Identifier::Key(&key)).await.unwrap(),
982            Some(10 | 20)
983        ));
984
985        let buffer = context.encode();
986        assert!(has_metric_value(&buffer, "items_tracked", 2));
987    }
988
989    #[test_traced]
990    fn test_put_multi_duplicate_key_prunable() {
991        let executor = deterministic::Runner::default();
992        executor.start(|context| async move {
993            let archive = create_prunable(context.child("storage"), None).await;
994            test_put_multi_duplicate_key_impl(context, archive).await;
995        });
996    }
997
998    async fn test_get_all_impl(mut archive: impl MultiArchive<Key = FixedBytes<64>, Value = i32>) {
999        // Three items at the same index
1000        archive.put_multi(5, test_key("aaa"), 10).await.unwrap();
1001        archive.put_multi(5, test_key("bbb"), 20).await.unwrap();
1002        archive.put_multi(5, test_key("ccc"), 30).await.unwrap();
1003
1004        // One item at a different index
1005        archive.put_multi(7, test_key("ddd"), 40).await.unwrap();
1006
1007        // get_all returns all values at the index in insertion order
1008        let all = archive.get_all(5).await.unwrap();
1009        assert_eq!(all, Some(vec![10, 20, 30]));
1010
1011        // Single-item index returns one element
1012        let all = archive.get_all(7).await.unwrap();
1013        assert_eq!(all, Some(vec![40]));
1014
1015        // Missing index returns None
1016        let all = archive.get_all(99).await.unwrap();
1017        assert_eq!(all, None);
1018
1019        // Archive::get(Index) still returns only the first
1020        assert_eq!(archive.get(Identifier::Index(5)).await.unwrap(), Some(10));
1021    }
1022
1023    #[test_traced]
1024    fn test_get_all_prunable() {
1025        let executor = deterministic::Runner::default();
1026        executor.start(|context| async move {
1027            let archive = create_prunable(context, None).await;
1028            test_get_all_impl(archive).await;
1029        });
1030    }
1031
1032    async fn test_put_multi_preserves_archive_put_semantics_impl(
1033        mut archive: impl MultiArchive<Key = FixedBytes<64>, Value = i32>,
1034    ) {
1035        // put_multi two items at the same index
1036        archive
1037            .put_multi(1, test_key("aaa"), 10)
1038            .await
1039            .expect("put_multi");
1040        archive
1041            .put_multi(1, test_key("bbb"), 20)
1042            .await
1043            .expect("put_multi");
1044
1045        // Archive::put is a no-op when index already exists
1046        archive
1047            .put(1, test_key("ccc"), 30)
1048            .await
1049            .expect("Archive::put should no-op");
1050
1051        // Only two items exist (Archive::put did not add a third)
1052        assert_eq!(
1053            archive
1054                .get(Identifier::Key(&test_key("aaa")))
1055                .await
1056                .unwrap(),
1057            Some(10)
1058        );
1059        assert_eq!(
1060            archive
1061                .get(Identifier::Key(&test_key("bbb")))
1062                .await
1063                .unwrap(),
1064            Some(20)
1065        );
1066        assert_eq!(
1067            archive
1068                .get(Identifier::Key(&test_key("ccc")))
1069                .await
1070                .unwrap(),
1071            None
1072        );
1073
1074        // Archive::get(Index) returns the first item inserted
1075        let first = archive
1076            .get(Identifier::Index(1))
1077            .await
1078            .unwrap()
1079            .expect("should find first");
1080        assert_eq!(first, 10);
1081    }
1082
1083    #[test_traced]
1084    fn test_put_multi_preserves_archive_put_semantics_prunable() {
1085        let executor = deterministic::Runner::default();
1086        executor.start(|context| async move {
1087            let archive = create_prunable(context, None).await;
1088            test_put_multi_preserves_archive_put_semantics_impl(archive).await;
1089        });
1090    }
1091
1092    async fn test_put_multi_restart_impl<A, F, Fut>(
1093        context: Context,
1094        creator: F,
1095        compression: Option<u8>,
1096    ) where
1097        A: MultiArchive<Key = FixedBytes<64>, Value = i32>,
1098        F: Fn(Context, Option<u8>) -> Fut,
1099        Fut: Future<Output = A>,
1100    {
1101        // Write multi-items, sync, and drop
1102        {
1103            let mut archive = creator(
1104                context.child("init").with_attribute("index", 1),
1105                compression,
1106            )
1107            .await;
1108            archive.put_multi(5, test_key("aaa"), 10).await.unwrap();
1109            archive.put_multi(5, test_key("bbb"), 20).await.unwrap();
1110            archive.put_multi(7, test_key("ccc"), 30).await.unwrap();
1111            archive.sync().await.unwrap();
1112        }
1113
1114        // Reinitialize and verify
1115        let archive = creator(
1116            context.child("init").with_attribute("index", 2),
1117            compression,
1118        )
1119        .await;
1120
1121        assert_eq!(
1122            archive
1123                .get(Identifier::Key(&test_key("aaa")))
1124                .await
1125                .unwrap(),
1126            Some(10)
1127        );
1128        assert_eq!(
1129            archive
1130                .get(Identifier::Key(&test_key("bbb")))
1131                .await
1132                .unwrap(),
1133            Some(20)
1134        );
1135        assert_eq!(
1136            archive
1137                .get(Identifier::Key(&test_key("ccc")))
1138                .await
1139                .unwrap(),
1140            Some(30)
1141        );
1142
1143        // items_tracked reflects two unique indices after restart
1144        let buffer = context.encode();
1145        assert!(has_metric_value(&buffer, "items_tracked", 2));
1146    }
1147
1148    #[test_traced]
1149    fn test_put_multi_restart_prunable() {
1150        let executor = deterministic::Runner::default();
1151        executor.start(|context| async move {
1152            test_put_multi_restart_impl(context, create_prunable, None).await;
1153        });
1154    }
1155
1156    async fn test_put_multi_mixed_indices_impl(
1157        context: Context,
1158        mut archive: impl MultiArchive<Key = FixedBytes<64>, Value = i32>,
1159    ) {
1160        // Mix Archive::put (single-item) and MultiArchive::put_multi
1161        archive.put(1, test_key("single"), 100).await.unwrap();
1162        archive
1163            .put_multi(2, test_key("multi-a"), 200)
1164            .await
1165            .unwrap();
1166        archive
1167            .put_multi(2, test_key("multi-b"), 201)
1168            .await
1169            .unwrap();
1170        archive
1171            .put_multi(3, test_key("multi-c"), 300)
1172            .await
1173            .unwrap();
1174
1175        // All retrievable by key
1176        assert_eq!(
1177            archive
1178                .get(Identifier::Key(&test_key("single")))
1179                .await
1180                .unwrap(),
1181            Some(100)
1182        );
1183        assert_eq!(
1184            archive
1185                .get(Identifier::Key(&test_key("multi-a")))
1186                .await
1187                .unwrap(),
1188            Some(200)
1189        );
1190        assert_eq!(
1191            archive
1192                .get(Identifier::Key(&test_key("multi-b")))
1193                .await
1194                .unwrap(),
1195            Some(201)
1196        );
1197        assert_eq!(
1198            archive
1199                .get(Identifier::Key(&test_key("multi-c")))
1200                .await
1201                .unwrap(),
1202            Some(300)
1203        );
1204
1205        // Archive::get(Index) returns first item at that index
1206        assert_eq!(archive.get(Identifier::Index(2)).await.unwrap(), Some(200));
1207
1208        // Gap tracking works across mixed usage
1209        let (end, next) = archive.next_gap(1);
1210        assert_eq!(end, Some(3));
1211        assert!(next.is_none());
1212
1213        let buffer = context.encode();
1214        assert!(has_metric_value(&buffer, "items_tracked", 3));
1215    }
1216
1217    #[test_traced]
1218    fn test_put_multi_mixed_indices_prunable() {
1219        let executor = deterministic::Runner::default();
1220        executor.start(|context| async move {
1221            let archive = create_prunable(context.child("storage"), None).await;
1222            test_put_multi_mixed_indices_impl(context, archive).await;
1223        });
1224    }
1225
1226    fn assert_send<T: Send>(_: T) {}
1227
1228    #[allow(dead_code)]
1229    fn assert_archive_futures_are_send<T: super::Archive>(
1230        archive: &mut T,
1231        key: T::Key,
1232        value: T::Value,
1233    ) where
1234        T::Key: Clone,
1235        T::Value: Clone,
1236    {
1237        assert_send(archive.put(1, key.clone(), value.clone()));
1238        assert_send(archive.put_sync(2, key.clone(), value.clone()));
1239        assert_send(archive.put_start_sync(3, key.clone(), value));
1240        assert_send(archive.get(Identifier::Index(1)));
1241        assert_send(archive.get(Identifier::Key(&key)));
1242        assert_send(archive.has(Identifier::Index(1)));
1243        assert_send(archive.has(Identifier::Key(&key)));
1244        assert_send(archive.sync());
1245        assert_send(archive.start_sync());
1246    }
1247
1248    #[allow(dead_code)]
1249    fn assert_archive_destroy_is_send<T: super::Archive>(archive: T) {
1250        assert_send(archive.destroy());
1251    }
1252
1253    #[allow(dead_code)]
1254    fn assert_multi_archive_futures_are_send<T: super::MultiArchive>(
1255        archive: &mut T,
1256        key: T::Key,
1257        value: T::Value,
1258    ) where
1259        T::Key: Clone,
1260        T::Value: Clone,
1261    {
1262        assert_archive_futures_are_send(archive, key.clone(), value.clone());
1263        assert_send(archive.get_all(1));
1264        assert_send(archive.put_multi(1, key.clone(), value.clone()));
1265        assert_send(archive.put_multi_sync(2, key.clone(), value.clone()));
1266        assert_send(archive.put_multi_start_sync(3, key, value));
1267    }
1268
1269    #[allow(dead_code)]
1270    fn assert_prunable_archive_futures_are_send(
1271        archive: &mut prunable::Archive<TwoCap, Context, FixedBytes<64>, i32>,
1272        key: FixedBytes<64>,
1273        value: i32,
1274    ) {
1275        assert_archive_futures_are_send(archive, key, value);
1276    }
1277
1278    #[allow(dead_code)]
1279    fn assert_prunable_multi_archive_futures_are_send(
1280        archive: &mut prunable::Archive<TwoCap, Context, FixedBytes<64>, i32>,
1281        key: FixedBytes<64>,
1282        value: i32,
1283    ) {
1284        assert_multi_archive_futures_are_send(archive, key, value);
1285    }
1286
1287    #[allow(dead_code)]
1288    fn assert_prunable_archive_destroy_is_send(
1289        archive: prunable::Archive<TwoCap, Context, FixedBytes<64>, i32>,
1290    ) {
1291        assert_archive_destroy_is_send(archive);
1292    }
1293
1294    #[allow(dead_code)]
1295    fn assert_immutable_archive_futures_are_send(
1296        archive: &mut immutable::Archive<Context, FixedBytes<64>, i32>,
1297        key: FixedBytes<64>,
1298        value: i32,
1299    ) {
1300        assert_archive_futures_are_send(archive, key, value);
1301    }
1302
1303    #[allow(dead_code)]
1304    fn assert_immutable_archive_destroy_is_send(
1305        archive: immutable::Archive<Context, FixedBytes<64>, i32>,
1306    ) {
1307        assert_archive_destroy_is_send(archive);
1308    }
1309}