1use crate::{deterministic::BoxDynRng, Error, IoBufs, IoBufsMut};
4use bytes::Buf;
5use commonware_utils::sync::{Mutex, RwLock};
6use rand::Rng;
7use std::{
8 io::Error as IoError,
9 sync::{
10 atomic::{AtomicU64, Ordering},
11 Arc,
12 },
13};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17enum Op {
18 Open,
19 Read,
20 Write,
21 Sync,
22 Resize,
23 Remove,
24 Scan,
25}
26
27#[derive(Clone, Debug, Default)]
31pub struct Config {
32 pub open_rate: Option<f64>,
34
35 pub read_rate: Option<f64>,
37
38 pub write_rate: Option<f64>,
40
41 pub partial_write_rate: Option<f64>,
46
47 pub sync_rate: Option<f64>,
49
50 pub resize_rate: Option<f64>,
52
53 pub partial_resize_rate: Option<f64>,
58
59 pub remove_rate: Option<f64>,
61
62 pub scan_rate: Option<f64>,
64}
65
66impl Config {
67 fn rate_for(&self, op: Op) -> f64 {
69 match op {
70 Op::Open => self.open_rate,
71 Op::Read => self.read_rate,
72 Op::Write => self.write_rate,
73 Op::Sync => self.sync_rate,
74 Op::Resize => self.resize_rate,
75 Op::Remove => self.remove_rate,
76 Op::Scan => self.scan_rate,
77 }
78 .unwrap_or(0.0)
79 }
80
81 pub const fn open(mut self, rate: f64) -> Self {
83 self.open_rate = Some(rate);
84 self
85 }
86
87 pub const fn read(mut self, rate: f64) -> Self {
89 self.read_rate = Some(rate);
90 self
91 }
92
93 pub const fn write(mut self, rate: f64) -> Self {
95 self.write_rate = Some(rate);
96 self
97 }
98
99 pub const fn partial_write(mut self, rate: f64) -> Self {
101 self.partial_write_rate = Some(rate);
102 self
103 }
104
105 pub const fn sync(mut self, rate: f64) -> Self {
107 self.sync_rate = Some(rate);
108 self
109 }
110
111 pub const fn resize(mut self, rate: f64) -> Self {
113 self.resize_rate = Some(rate);
114 self
115 }
116
117 pub const fn partial_resize(mut self, rate: f64) -> Self {
119 self.partial_resize_rate = Some(rate);
120 self
121 }
122
123 pub const fn remove(mut self, rate: f64) -> Self {
125 self.remove_rate = Some(rate);
126 self
127 }
128
129 pub const fn scan(mut self, rate: f64) -> Self {
131 self.scan_rate = Some(rate);
132 self
133 }
134}
135
136#[derive(Clone)]
138struct Oracle {
139 rng: Arc<Mutex<BoxDynRng>>,
140 config: Arc<RwLock<Config>>,
141}
142
143impl Oracle {
144 fn should_fail(&self, op: Op) -> bool {
146 self.roll(Some(self.config.read().rate_for(op)))
147 }
148
149 fn check_write_fault(&self) -> (bool, Option<f64>) {
152 let config = self.config.read();
153 let fail = self.roll(Some(config.rate_for(Op::Write)));
154 (fail, config.partial_write_rate)
155 }
156
157 fn check_resize_fault(&self) -> (bool, Option<f64>) {
160 let config = self.config.read();
161 let fail = self.roll(Some(config.rate_for(Op::Resize)));
162 (fail, config.partial_resize_rate)
163 }
164
165 fn roll(&self, rate: Option<f64>) -> bool {
167 let rate = rate.unwrap_or(0.0);
168 if rate <= 0.0 {
169 return false;
170 }
171 if rate >= 1.0 {
172 return true;
173 }
174 self.rng.lock().gen::<f64>() < rate
175 }
176
177 fn random_between(&self, from: u64, to: u64) -> Option<u64> {
179 if from == to {
180 return None;
181 }
182 let (min, max) = if from < to { (from, to) } else { (to, from) };
183 if max - min <= 1 {
184 return None;
185 }
186 Some(self.rng.lock().gen_range(min + 1..max))
187 }
188
189 fn try_partial(&self, rate: Option<f64>, from: u64, to: u64) -> Option<u64> {
192 if self.roll(rate) {
193 self.random_between(from, to)
194 } else {
195 None
196 }
197 }
198}
199
200#[derive(Clone)]
204pub struct Storage<S: crate::Storage> {
205 inner: S,
206 ctx: Oracle,
207}
208
209impl<S: crate::Storage> Storage<S> {
210 pub fn new(inner: S, rng: Arc<Mutex<BoxDynRng>>, config: Arc<RwLock<Config>>) -> Self {
212 Self {
213 inner,
214 ctx: Oracle { rng, config },
215 }
216 }
217
218 pub const fn inner(&self) -> &S {
220 &self.inner
221 }
222
223 pub fn config(&self) -> Arc<RwLock<Config>> {
225 self.ctx.config.clone()
226 }
227}
228
229fn injected_io_error() -> IoError {
231 IoError::other("injected storage fault")
232}
233
234impl<S: crate::Storage> crate::Storage for Storage<S> {
235 type Blob = Blob<S::Blob>;
236
237 async fn open_versioned(
238 &self,
239 partition: &str,
240 name: &[u8],
241 versions: std::ops::RangeInclusive<u16>,
242 ) -> Result<(Self::Blob, u64, u16), Error> {
243 if self.ctx.should_fail(Op::Open) {
244 return Err(Error::Io(injected_io_error()));
245 }
246 self.inner
247 .open_versioned(partition, name, versions)
248 .await
249 .map(|(blob, len, blob_version)| {
250 (Blob::new(self.ctx.clone(), blob, len), len, blob_version)
251 })
252 }
253
254 async fn remove(&self, partition: &str, name: Option<&[u8]>) -> Result<(), Error> {
255 if self.ctx.should_fail(Op::Remove) {
256 return Err(Error::Io(injected_io_error()));
257 }
258 self.inner.remove(partition, name).await
259 }
260
261 async fn scan(&self, partition: &str) -> Result<Vec<Vec<u8>>, Error> {
262 if self.ctx.should_fail(Op::Scan) {
263 return Err(Error::Io(injected_io_error()));
264 }
265 self.inner.scan(partition).await
266 }
267}
268
269#[derive(Clone)]
271pub struct Blob<B: crate::Blob> {
272 inner: B,
273 ctx: Oracle,
274 size: Arc<AtomicU64>,
276}
277
278impl<B: crate::Blob> Blob<B> {
279 fn new(ctx: Oracle, inner: B, size: u64) -> Self {
280 Self {
281 inner,
282 ctx,
283 size: Arc::new(AtomicU64::new(size)),
284 }
285 }
286}
287
288impl<B: crate::Blob> crate::Blob for Blob<B> {
289 async fn read_at(&self, offset: u64, len: usize) -> Result<IoBufsMut, Error> {
290 if self.ctx.should_fail(Op::Read) {
291 return Err(Error::Io(injected_io_error()));
292 }
293 self.inner.read_at(offset, len).await
294 }
295
296 async fn read_at_buf(
297 &self,
298 offset: u64,
299 len: usize,
300 bufs: impl Into<IoBufsMut> + Send,
301 ) -> Result<IoBufsMut, Error> {
302 if self.ctx.should_fail(Op::Read) {
303 return Err(Error::Io(injected_io_error()));
304 }
305 self.inner.read_at_buf(offset, len, bufs.into()).await
306 }
307
308 async fn write_at(&self, offset: u64, bufs: impl Into<IoBufs> + Send) -> Result<(), Error> {
309 let bufs = bufs.into();
310 let total_bytes = bufs.remaining() as u64;
311
312 let (should_fail, partial_rate) = self.ctx.check_write_fault();
313 if should_fail {
314 if let Some(bytes) = self.ctx.try_partial(partial_rate, 0, total_bytes) {
315 self.inner
317 .write_at(offset, bufs.coalesce().slice(..bytes as usize))
318 .await?;
319 self.inner.sync().await?;
320 self.size
321 .fetch_max(offset.saturating_add(bytes), Ordering::Relaxed);
322 return Err(Error::Io(injected_io_error()));
323 }
324 return Err(Error::Io(injected_io_error()));
325 }
326
327 self.inner.write_at(offset, bufs).await?;
328 self.size
329 .fetch_max(offset.saturating_add(total_bytes), Ordering::Relaxed);
330 Ok(())
331 }
332
333 async fn write_at_sync(
334 &self,
335 offset: u64,
336 bufs: impl Into<IoBufs> + Send,
337 ) -> Result<(), Error> {
338 let bufs = bufs.into();
339 let total_bytes = bufs.remaining() as u64;
340 if total_bytes == 0 {
341 return Ok(());
342 }
343
344 let (should_fail, partial_rate) = self.ctx.check_write_fault();
345 if should_fail {
346 if let Some(bytes) = self.ctx.try_partial(partial_rate, 0, total_bytes) {
347 self.inner
348 .write_at_sync(offset, bufs.coalesce().slice(..bytes as usize))
349 .await?;
350 self.size
351 .fetch_max(offset.saturating_add(bytes), Ordering::Relaxed);
352 return Err(Error::Io(injected_io_error()));
353 }
354 return Err(Error::Io(injected_io_error()));
355 }
356
357 if self.ctx.should_fail(Op::Sync) {
358 self.inner.write_at(offset, bufs).await?;
359 self.size
360 .fetch_max(offset.saturating_add(total_bytes), Ordering::Relaxed);
361 return Err(Error::Io(injected_io_error()));
362 }
363
364 self.inner.write_at_sync(offset, bufs).await?;
365 self.size
366 .fetch_max(offset.saturating_add(total_bytes), Ordering::Relaxed);
367 Ok(())
368 }
369
370 async fn resize(&self, len: u64) -> Result<(), Error> {
371 let (should_fail, partial_rate) = self.ctx.check_resize_fault();
372 if should_fail {
373 let current = self.size.load(Ordering::Relaxed);
374 if let Some(len) = self.ctx.try_partial(partial_rate, current, len) {
375 self.inner.resize(len).await?;
377 self.inner.sync().await?;
378 self.size.store(len, Ordering::Relaxed);
379 return Err(Error::Io(injected_io_error()));
380 }
381 return Err(Error::Io(injected_io_error()));
382 }
383 self.inner.resize(len).await?;
384 self.size.store(len, Ordering::Relaxed);
385 Ok(())
386 }
387
388 async fn sync(&self) -> Result<(), Error> {
389 if self.ctx.should_fail(Op::Sync) {
390 return Err(Error::Io(injected_io_error()));
391 }
392 self.inner.sync().await
393 }
394}
395
396#[cfg(test)]
397mod tests {
398 use super::*;
399 use crate::{
400 storage::{memory::Storage as MemStorage, tests::run_storage_tests},
401 telemetry::metrics::Registry,
402 Blob as _, BufferPool, BufferPoolConfig, Storage as _,
403 };
404 use rand::{rngs::StdRng, SeedableRng};
405
406 fn test_pool() -> BufferPool {
407 let mut registry = Registry::default();
408 BufferPool::new(BufferPoolConfig::for_storage(), &mut registry)
409 }
410
411 struct Harness {
413 inner: MemStorage,
414 storage: Storage<MemStorage>,
415 config: Arc<RwLock<Config>>,
416 }
417
418 impl Harness {
419 fn new(config: Config) -> Self {
420 Self::with_seed(42, config)
421 }
422
423 fn with_seed(seed: u64, config: Config) -> Self {
424 let inner = MemStorage::new(test_pool());
425 let rng = Arc::new(Mutex::new(
426 Box::new(StdRng::seed_from_u64(seed)) as BoxDynRng
427 ));
428 let config = Arc::new(RwLock::new(config));
429 let storage = Storage::new(inner.clone(), rng, config.clone());
430 Self {
431 inner,
432 storage,
433 config,
434 }
435 }
436 }
437
438 #[tokio::test]
439 async fn test_faulty_storage_no_faults() {
440 let h = Harness::new(Config::default());
441 run_storage_tests(h.storage).await;
442 }
443
444 #[tokio::test]
445 async fn test_faulty_storage_sync_always_fails() {
446 let h = Harness::new(Config::default().sync(1.0));
447
448 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
449 blob.write_at(0, b"data".to_vec()).await.unwrap();
450
451 assert!(matches!(blob.sync().await, Err(Error::Io(_))));
452 }
453
454 #[tokio::test]
455 async fn test_faulty_storage_write_always_fails() {
456 let h = Harness::new(Config::default().write(1.0));
457
458 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
459
460 assert!(matches!(
461 blob.write_at(0, b"data".to_vec()).await,
462 Err(Error::Io(_))
463 ));
464 }
465
466 #[tokio::test]
467 async fn test_faulty_storage_write_at_sync_write_always_fails() {
468 let h = Harness::new(Config::default().write(1.0));
469
470 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
471
472 assert!(matches!(
473 blob.write_at_sync(0, b"data".to_vec()).await,
474 Err(Error::Io(_))
475 ));
476 }
477
478 #[tokio::test]
479 async fn test_faulty_storage_write_at_sync_sync_failure_is_not_durable() {
480 let h = Harness::new(Config::default().sync(1.0));
481
482 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
483
484 assert!(matches!(
485 blob.write_at_sync(0, b"data".to_vec()).await,
486 Err(Error::Io(_))
487 ));
488
489 let (_reopened, size) = h.inner.open("partition", b"test").await.unwrap();
490 assert_eq!(size, 0);
491 }
492
493 #[tokio::test]
494 async fn test_faulty_storage_empty_write_at_sync_does_not_sync_prior_write() {
495 let h = Harness::new(Config::default().sync(1.0));
496
497 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
498 blob.write_at(0, b"data".to_vec()).await.unwrap();
499
500 blob.write_at_sync(4, Vec::<u8>::new()).await.unwrap();
501
502 let (_reopened, size) = h.inner.open("partition", b"test").await.unwrap();
503 assert_eq!(size, 0);
504 }
505
506 #[tokio::test]
507 async fn test_faulty_storage_read_always_fails() {
508 let h = Harness::new(Config::default());
509
510 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
512 blob.write_at(0, b"data".to_vec()).await.unwrap();
513 blob.sync().await.unwrap();
514
515 h.config.write().read_rate = Some(1.0);
517
518 assert!(matches!(blob.read_at(0, 4).await, Err(Error::Io(_))));
519 }
520
521 #[tokio::test]
522 async fn test_faulty_storage_open_always_fails() {
523 let h = Harness::new(Config::default().open(1.0));
524
525 assert!(matches!(
526 h.storage.open("partition", b"test").await,
527 Err(Error::Io(_))
528 ));
529 }
530
531 #[tokio::test]
532 async fn test_faulty_storage_remove_always_fails() {
533 let h = Harness::new(Config::default());
534
535 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
537 blob.write_at(0, b"data".to_vec()).await.unwrap();
538 blob.sync().await.unwrap();
539 drop(blob);
540
541 h.config.write().remove_rate = Some(1.0);
543
544 assert!(matches!(
545 h.storage.remove("partition", Some(b"test")).await,
546 Err(Error::Io(_))
547 ));
548 }
549
550 #[tokio::test]
551 async fn test_faulty_storage_scan_always_fails() {
552 let h = Harness::new(Config::default());
553
554 for i in 0..3 {
556 let name = format!("blob{i}");
557 let (blob, _) = h.storage.open("partition", name.as_bytes()).await.unwrap();
558 blob.write_at(0, b"data".to_vec()).await.unwrap();
559 blob.sync().await.unwrap();
560 }
561
562 h.config.write().scan_rate = Some(1.0);
564
565 assert!(matches!(
566 h.storage.scan("partition").await,
567 Err(Error::Io(_))
568 ));
569 }
570
571 #[tokio::test]
572 async fn test_faulty_storage_determinism() {
573 async fn run_ops(seed: u64, rate: f64) -> Vec<bool> {
574 let h = Harness::with_seed(seed, Config::default().open(rate));
575 let mut results = Vec::new();
576 for i in 0..20 {
577 let name = format!("blob{i}");
578 results.push(h.storage.open("partition", name.as_bytes()).await.is_ok());
579 }
580 results
581 }
582
583 let results1 = run_ops(42, 0.5).await;
584 let results2 = run_ops(42, 0.5).await;
585 assert_eq!(results1, results2, "Same seed should produce same results");
586
587 let results3 = run_ops(999, 0.5).await;
588 assert_ne!(
589 results1, results3,
590 "Different seeds should produce different results"
591 );
592 }
593
594 #[tokio::test]
595 async fn test_faulty_storage_rate_for() {
596 let config = Config::default().open(0.1).sync(0.9);
597
598 assert!((config.rate_for(Op::Open) - 0.1).abs() < f64::EPSILON);
599 assert!((config.rate_for(Op::Sync) - 0.9).abs() < f64::EPSILON);
600 assert!(config.rate_for(Op::Write).abs() < f64::EPSILON);
601 }
602
603 #[tokio::test]
604 async fn test_faulty_storage_dynamic_config() {
605 let h = Harness::new(Config::default());
606
607 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
608 blob.sync().await.unwrap();
609
610 h.config.write().sync_rate = Some(1.0);
611 assert!(matches!(blob.sync().await, Err(Error::Io(_))));
612
613 h.config.write().sync_rate = Some(0.0);
614 blob.sync().await.unwrap();
615 }
616
617 #[tokio::test]
618 async fn test_faulty_storage_partial_write() {
619 let h = Harness::new(Config::default().write(1.0).partial_write(1.0));
620
621 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
622 let data = b"hello world".to_vec();
623 let result = blob.write_at(0, data.clone()).await;
624
625 assert!(matches!(result, Err(Error::Io(_))));
626
627 let (inner_blob, size) = h.inner.open("partition", b"test").await.unwrap();
628 let bytes_written = size as usize;
629 assert!(
630 bytes_written > 0 && bytes_written < data.len(),
631 "Expected partial write: {bytes_written} bytes out of {}",
632 data.len()
633 );
634
635 let read_result = inner_blob.read_at(0, bytes_written).await.unwrap();
636 assert_eq!(read_result.coalesce().as_ref(), &data[..bytes_written]);
637 }
638
639 #[tokio::test]
640 async fn test_faulty_storage_partial_write_disabled() {
641 let h = Harness::new(Config::default().write(1.0).partial_write(0.0));
642
643 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
644 let result = blob.write_at(0, b"hello world".to_vec()).await;
645
646 assert!(matches!(result, Err(Error::Io(_))));
647
648 let (_, size) = h.inner.open("partition", b"test").await.unwrap();
649 assert_eq!(
650 size, 0,
651 "Expected no bytes written when partial_write_rate is 0"
652 );
653 }
654
655 #[tokio::test]
656 async fn test_faulty_storage_partial_write_single_byte() {
657 let h = Harness::new(Config::default().write(1.0).partial_write(1.0));
658
659 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
660 let result = blob.write_at(0, b"x".to_vec()).await;
661
662 assert!(matches!(result, Err(Error::Io(_))));
663
664 let (_, size) = h.inner.open("partition", b"test").await.unwrap();
665 assert_eq!(size, 0, "No partial write possible for single byte");
666 }
667
668 #[tokio::test]
669 async fn test_faulty_storage_partial_resize_grow() {
670 let h = Harness::new(Config::default().resize(1.0).partial_resize(1.0));
671
672 let (blob, initial_size) = h.storage.open("partition", b"test").await.unwrap();
673 assert_eq!(initial_size, 0);
674
675 let target_size = 100u64;
676 let result = blob.resize(target_size).await;
677
678 assert!(matches!(result, Err(Error::Io(_))));
679
680 let (_, actual_size) = h.inner.open("partition", b"test").await.unwrap();
681 assert!(
682 actual_size > 0 && actual_size < target_size,
683 "Expected partial resize: size {actual_size} should be between 0 and {target_size}"
684 );
685 }
686
687 #[tokio::test]
688 async fn test_faulty_storage_partial_resize_shrink() {
689 let h = Harness::new(Config::default());
690
691 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
692 blob.resize(100).await.unwrap();
693 blob.sync().await.unwrap();
694
695 {
696 let mut cfg = h.config.write();
697 cfg.resize_rate = Some(1.0);
698 cfg.partial_resize_rate = Some(1.0);
699 }
700
701 let target_size = 10u64;
702 let result = blob.resize(target_size).await;
703
704 assert!(matches!(result, Err(Error::Io(_))));
705
706 let (_, actual_size) = h.inner.open("partition", b"test").await.unwrap();
707 assert!(
708 actual_size > target_size && actual_size < 100,
709 "Expected partial shrink: size {actual_size} should be between {target_size} and 100"
710 );
711 }
712
713 #[tokio::test]
714 async fn test_faulty_storage_partial_resize_disabled() {
715 let h = Harness::new(Config::default().resize(1.0).partial_resize(0.0));
716
717 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
718 let result = blob.resize(100).await;
719
720 assert!(matches!(result, Err(Error::Io(_))));
721
722 let (_, size) = h.inner.open("partition", b"test").await.unwrap();
723 assert_eq!(size, 0, "Expected no resize when partial_resize_rate is 0");
724 }
725
726 #[tokio::test]
727 async fn test_faulty_storage_partial_resize_same_size() {
728 let h = Harness::new(Config::default().resize(1.0).partial_resize(1.0));
729
730 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
731 let result = blob.resize(0).await;
732
733 assert!(matches!(result, Err(Error::Io(_))));
734
735 let (_, size) = h.inner.open("partition", b"test").await.unwrap();
736 assert_eq!(size, 0);
737 }
738
739 #[tokio::test]
740 async fn test_faulty_storage_partial_resize_after_write_extends() {
741 let h = Harness::new(Config::default());
742
743 let (blob, initial_size) = h.storage.open("partition", b"test").await.unwrap();
744 assert_eq!(initial_size, 0);
745
746 blob.write_at(0, vec![0xABu8; 50]).await.unwrap();
747 blob.sync().await.unwrap();
748
749 let (_, size_after_write) = h.inner.open("partition", b"test").await.unwrap();
750 assert_eq!(size_after_write, 50);
751
752 {
753 let mut cfg = h.config.write();
754 cfg.resize_rate = Some(1.0);
755 cfg.partial_resize_rate = Some(1.0);
756 }
757
758 let target_size = 10u64;
759 let result = blob.resize(target_size).await;
760
761 assert!(matches!(result, Err(Error::Io(_))));
762
763 let (_, actual_size) = h.inner.open("partition", b"test").await.unwrap();
764 assert!(
765 actual_size > target_size && actual_size < 50,
766 "Expected partial shrink from 50: size {actual_size} should be between {target_size} and 50"
767 );
768 }
769
770 #[tokio::test]
771 async fn test_faulty_storage_partial_resize_one_byte_difference() {
772 let h = Harness::new(Config::default().resize(1.0).partial_resize(1.0));
773
774 let (blob, _) = h.storage.open("partition", b"test").await.unwrap();
775 let result = blob.resize(1).await;
776
777 assert!(matches!(result, Err(Error::Io(_))));
778
779 let (_, size) = h.inner.open("partition", b"test").await.unwrap();
780 assert_eq!(size, 0);
781 }
782}