@@ -28,7 +28,10 @@ use crate::{
2828use ahash:: AHasher ;
2929use commonware_codec:: Codec ;
3030use commonware_cryptography:: { Digest , Hasher } ;
31- use commonware_utils:: bitmap:: { Prunable as BitMap , Readable as BitmapReadable } ;
31+ use commonware_utils:: {
32+ bitmap:: { Prunable as BitMap , Readable as BitmapReadable } ,
33+ sync:: { RwLock , RwLockReadGuard , RwLockWriteGuard } ,
34+ } ;
3235use std:: {
3336 collections:: { BTreeSet , HashMap } ,
3437 hash:: BuildHasherDefault ,
@@ -279,11 +282,34 @@ where
279282 bitmap_parent : BitmapBatch < N > ,
280283}
281284
282- /// A speculative batch of operations whose root digest has been computed,
283- /// in contrast to [`UnmerkleizedBatch`].
285+ /// A speculative batch of operations whose root digest has been computed, in contrast to
286+ /// [`UnmerkleizedBatch`].
287+ ///
288+ /// Wraps an [`any::batch::MerkleizedBatch`] and adds the bitmap and grafted MMR state needed to
289+ /// compute the canonical root.
290+ ///
291+ /// # Stale reads
292+ ///
293+ /// A `MerkleizedBatch` holds a reference to the DB's committed bitmap via [`BitmapBatch::Base`].
294+ /// That bitmap evolves in place as [`Db::apply_batch`](super::db::Db::apply_batch) commits batches.
295+ /// Reads through this batch's chain (via its own `get`, by constructing child batches, or by
296+ /// anything that walks the bitmap chain) are only semantically correct if this batch's chain is
297+ /// still a valid prefix of the DB's committed state. That is, every
298+ /// [`apply_batch`](super::db::Db::apply_batch) call since this batch was merkleized has applied an
299+ /// ancestor of *this* batch.
300+ ///
301+ /// **The library does not guard against stale reads.** If you apply an unrelated sibling or any
302+ /// other non-ancestor batch and then read through this one, chunk reads fall through to the
303+ /// (now-advanced) committed bitmap while your overlays are still applied on top, producing
304+ /// incoherent state that doesn't correspond to any valid DB view.
284305///
285- /// Wraps an [`any::batch::MerkleizedBatch`] and adds the bitmap and grafted MMR state needed
286- /// to compute the canonical root.
306+ /// Rules of thumb:
307+ /// - Drop any `Arc<MerkleizedBatch>` you no longer intend to apply.
308+ /// - Extending a batch after `apply_batch` has consumed it (building a child off the just-applied
309+ /// parent) is safe. The committed bitmap now equals the parent's post-apply state, so child
310+ /// reads are consistent.
311+ /// - Extending a batch after a *different* branch has been applied is not safe and will silently
312+ /// return wrong data.
287313pub struct MerkleizedBatch < F : Graftable , D : Digest , U : update:: Update + Send + Sync , const N : usize >
288314where
289315 Operation < F , U > : Send + Sync ,
@@ -595,7 +621,6 @@ where
595621 // compute_db_root sees newly completed chunks. Using bitmap_parent alone would miss chunks
596622 // that transitioned from partial to complete in this batch.
597623 let bitmap_batch = BitmapBatch :: Layer ( Arc :: new ( BitmapBatchLayer {
598- pruned_chunks : bitmap_parent. pruned_chunks ( ) ,
599624 parent : bitmap_parent. clone ( ) ,
600625 overlay : Arc :: new ( overlay) ,
601626 } ) ) ;
@@ -637,13 +662,86 @@ where
637662 } ) )
638663}
639664
640- /// Immutable bitmap state at any point in a batch chain.
665+ /// The committed bitmap shared between the [`Db`](super::db::Db) and live batches.
666+ ///
667+ /// Wrapped in a [`RwLock`] so that [`Db::apply_batch`](super::db::Db::apply_batch),
668+ /// [`Db::prune`](super::db::Db::prune), and [`Db::rewind`](super::db::Db::rewind) can mutate
669+ /// the bitmap in place while live batches concurrently read through it. Batches never write;
670+ /// write access is kept module-private to `current::db`.
671+ ///
672+ /// # Stale reads
673+ ///
674+ /// The bitmap behind this lock represents *committed* state. If a caller holds a `MerkleizedBatch`
675+ /// whose chain is no longer a valid prefix of committed state (e.g., an orphaned sibling branch
676+ /// after the other branch was applied), reads through that chain's `Base` will silently return
677+ /// inconsistent data (the chain's overlays mixed with post-divergence committed chunks). The
678+ /// library does not guard against this; callers must avoid reading through stale batches.
679+ pub ( crate ) struct SharedBitmap < const N : usize > {
680+ inner : RwLock < BitMap < N > > ,
681+ }
682+
683+ impl < const N : usize > SharedBitmap < N > {
684+ pub ( crate ) const fn new ( bitmap : BitMap < N > ) -> Self {
685+ Self {
686+ inner : RwLock :: new ( bitmap) ,
687+ }
688+ }
689+
690+ /// Acquire a shared read guard over the committed bitmap.
691+ pub ( crate ) fn read ( & self ) -> RwLockReadGuard < ' _ , BitMap < N > > {
692+ self . inner . read ( )
693+ }
694+
695+ /// Acquire an exclusive write guard. Only [`Db::apply_batch`](super::db::Db::apply_batch),
696+ /// [`Db::prune`](super::db::Db::prune), and [`Db::rewind`](super::db::Db::rewind) mutate the
697+ /// shared bitmap.
698+ pub ( super ) fn write ( & self ) -> RwLockWriteGuard < ' _ , BitMap < N > > {
699+ self . inner . write ( )
700+ }
701+ }
702+
703+ impl < const N : usize > std:: fmt:: Debug for SharedBitmap < N > {
704+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
705+ f. debug_struct ( "SharedBitmap" )
706+ . field ( "bitmap_len" , & BitmapReadable :: < N > :: len ( & * self . read ( ) ) )
707+ . finish ( )
708+ }
709+ }
710+
711+ /// [`BitmapReadable`] over the DB's committed bitmap. Each call acquires the read lock briefly.
712+ impl < const N : usize > BitmapReadable < N > for SharedBitmap < N > {
713+ fn complete_chunks ( & self ) -> usize {
714+ self . read ( ) . complete_chunks ( )
715+ }
716+
717+ fn get_chunk ( & self , idx : usize ) -> [ u8 ; N ] {
718+ * self . read ( ) . get_chunk ( idx)
719+ }
720+
721+ fn last_chunk ( & self ) -> ( [ u8 ; N ] , u64 ) {
722+ let guard = self . read ( ) ;
723+ let ( chunk, bits) = guard. last_chunk ( ) ;
724+ ( * chunk, bits)
725+ }
726+
727+ fn pruned_chunks ( & self ) -> usize {
728+ self . read ( ) . pruned_chunks ( )
729+ }
730+
731+ fn len ( & self ) -> u64 {
732+ BitmapReadable :: < N > :: len ( & * self . read ( ) )
733+ }
734+ }
735+
736+ /// A view of the committed bitmap plus zero or more speculative overlay [`Layer`]s.
641737///
642- /// Mirrors the [`crate::merkle::batch::MerkleizedBatch`] pattern.
738+ /// The chain terminates in a [`Base`] that references the shared committed bitmap. No staleness
739+ /// check is performed. Callers must ensure they only read through batches whose chains are
740+ /// still valid prefixes of committed state (see [`SharedBitmap`]'s docs).
643741#[ derive( Clone , Debug ) ]
644742pub ( crate ) enum BitmapBatch < const N : usize > {
645- /// Committed bitmap (chain terminal) .
646- Base ( Arc < BitMap < N > > ) ,
743+ /// Chain terminal: shared reference to the committed bitmap .
744+ Base ( Arc < SharedBitmap < N > > ) ,
647745 /// Speculative layer on top of a parent batch.
648746 Layer ( Arc < BitmapBatchLayer < N > > ) ,
649747}
@@ -654,10 +752,6 @@ pub(crate) struct BitmapBatchLayer<const N: usize> {
654752 pub ( crate ) parent : BitmapBatch < N > ,
655753 /// Chunk-level overlay: materialized bytes for every chunk that differs from parent.
656754 pub ( crate ) overlay : Arc < ChunkOverlay < N > > ,
657- /// Pruned-chunk count, copied from `parent` at construction. Invariant across the whole
658- /// layer chain (pruning only happens on the committed base), so caching here lets
659- /// `BitmapBatch::pruned_chunks` return in O(1) instead of walking to the Base.
660- pub ( crate ) pruned_chunks : usize ,
661755}
662756
663757impl < const N : usize > BitmapBatch < N > {
@@ -675,7 +769,7 @@ impl<const N: usize> BitmapReadable<N> for BitmapBatch<N> {
675769 let mut current = self ;
676770 loop {
677771 match current {
678- Self :: Base ( bm ) => return * bm . get_chunk ( idx) ,
772+ Self :: Base ( shared ) => return * shared . read ( ) . get_chunk ( idx) ,
679773 Self :: Layer ( layer) => {
680774 if let Some ( & chunk) = layer. overlay . get ( idx) {
681775 return chunk;
@@ -703,95 +797,19 @@ impl<const N: usize> BitmapReadable<N> for BitmapBatch<N> {
703797
704798 fn pruned_chunks ( & self ) -> usize {
705799 match self {
706- Self :: Base ( bm ) => bm . pruned_chunks ( ) ,
707- Self :: Layer ( layer) => layer. pruned_chunks ,
800+ Self :: Base ( shared ) => shared . read ( ) . pruned_chunks ( ) ,
801+ Self :: Layer ( layer) => layer. parent . pruned_chunks ( ) ,
708802 }
709803 }
710804
711805 fn len ( & self ) -> u64 {
712806 match self {
713- Self :: Base ( bm ) => BitmapReadable :: < N > :: len ( bm . as_ref ( ) ) ,
807+ Self :: Base ( shared ) => BitmapReadable :: < N > :: len ( & * shared . read ( ) ) ,
714808 Self :: Layer ( layer) => layer. overlay . len ,
715809 }
716810 }
717811}
718812
719- impl < const N : usize > BitmapBatch < N > {
720- /// Apply a chunk overlay to this bitmap. When `self` is `Base` with sole ownership, writes
721- /// overlay chunks directly into the bitmap. Otherwise creates a new `Layer`.
722- pub ( super ) fn apply_overlay ( & mut self , overlay : Arc < ChunkOverlay < N > > ) {
723- // Fast path: write overlay chunks directly into the Base bitmap.
724- if let Self :: Base ( base) = self {
725- if let Some ( bitmap) = Arc :: get_mut ( base) {
726- // Extend bitmap to the overlay's length.
727- bitmap. extend_to ( overlay. len ) ;
728- // Overwrite dirty chunks.
729- for ( & idx, chunk_bytes) in & overlay. chunks {
730- if idx >= bitmap. pruned_chunks ( ) {
731- bitmap. set_chunk_by_index ( idx, chunk_bytes) ;
732- }
733- }
734- return ;
735- }
736- }
737-
738- // Slow path: create a new layer.
739- let pruned_chunks = self . pruned_chunks ( ) ;
740- let parent = self . clone ( ) ;
741- * self = Self :: Layer ( Arc :: new ( BitmapBatchLayer {
742- parent,
743- overlay,
744- pruned_chunks,
745- } ) ) ;
746- }
747-
748- /// Flatten all layers back to a single `Base(Arc<BitMap<N>>)`.
749- ///
750- /// After flattening, the new `Base` Arc has refcount 1 (assuming no external clones
751- /// are held).
752- pub ( super ) fn flatten ( & mut self ) {
753- if matches ! ( self , Self :: Base ( _) ) {
754- return ;
755- }
756-
757- // Take ownership of the chain so that Arc refcounts are not
758- // artificially inflated by a clone.
759- let mut owned = std:: mem:: replace ( self , Self :: Base ( Arc :: new ( BitMap :: default ( ) ) ) ) ;
760-
761- // Collect overlays from tip to base.
762- let mut overlays = Vec :: new ( ) ;
763- let base = loop {
764- match owned {
765- Self :: Base ( bm) => break bm,
766- Self :: Layer ( layer) => match Arc :: try_unwrap ( layer) {
767- Ok ( inner) => {
768- overlays. push ( inner. overlay ) ;
769- owned = inner. parent ;
770- }
771- Err ( arc) => {
772- overlays. push ( arc. overlay . clone ( ) ) ;
773- owned = arc. parent . clone ( ) ;
774- }
775- } ,
776- }
777- } ;
778-
779- // Apply overlays from base to tip.
780- let mut bitmap = Arc :: try_unwrap ( base) . unwrap_or_else ( |arc| ( * arc) . clone ( ) ) ;
781- for overlay in overlays. into_iter ( ) . rev ( ) {
782- // Extend bitmap to the overlay's length.
783- bitmap. extend_to ( overlay. len ) ;
784- // Apply dirty chunks.
785- for ( & idx, chunk_bytes) in & overlay. chunks {
786- if idx >= bitmap. pruned_chunks ( ) {
787- bitmap. set_chunk_by_index ( idx, chunk_bytes) ;
788- }
789- }
790- }
791- * self = Self :: Base ( Arc :: new ( bitmap) ) ;
792- }
793- }
794-
795813impl < F : Graftable , D : Digest , U : update:: Update + Send + Sync , const N : usize >
796814 MerkleizedBatch < F , D , U , N >
797815where
@@ -861,7 +879,7 @@ where
861879 Arc :: new ( MerkleizedBatch {
862880 inner : self . any . to_batch ( ) ,
863881 grafted,
864- bitmap : self . status . clone ( ) ,
882+ bitmap : BitmapBatch :: Base ( Arc :: clone ( & self . status ) ) ,
865883 canonical_root : self . root ,
866884 } )
867885 }
@@ -1301,33 +1319,4 @@ mod tests {
13011319 // Empty bitmap, tip = 0: no candidates.
13021320 assert_eq ! ( scan. next_candidate( Location :: new( 0 ) , 0 ) , None ) ;
13031321 }
1304-
1305- #[ test]
1306- fn test_apply_overlay ( ) {
1307- // Base: 8 bits all set, sole owner -> fast path.
1308- let base = make_bitmap ( & [ true ; 8 ] ) ;
1309- let mut batch = BitmapBatch :: Base ( Arc :: new ( base) ) ;
1310-
1311- let mut overlay = ChunkOverlay :: new ( 12 ) ;
1312- let mut c0 = [ 0u8 ; N ] ;
1313- c0[ 0 ] = 0b1111_0111 ; // bits 0-7 set except bit 3
1314- c0[ 1 ] = 0b0000_0100 ; // bit 10 set
1315- overlay. chunks . insert ( 0 , c0) ;
1316- batch. apply_overlay ( Arc :: new ( overlay) ) ;
1317-
1318- // Fast path keeps Base, extends length, applies chunks.
1319- assert ! ( matches!( batch, BitmapBatch :: Base ( _) ) ) ;
1320- assert_eq ! ( batch. len( ) , 12 ) ;
1321- assert_eq ! ( batch. get_chunk( 0 ) [ 0 ] & ( 1 << 3 ) , 0 ) ;
1322- assert_ne ! ( batch. get_chunk( 0 ) [ 1 ] & ( 1 << 2 ) , 0 ) ;
1323-
1324- // Shared Arc -> slow path creates Layer.
1325- let BitmapBatch :: Base ( ref base_arc) = batch else {
1326- panic ! ( "expected Base" ) ;
1327- } ;
1328- let _shared = Arc :: clone ( base_arc) ;
1329- let overlay2 = ChunkOverlay :: new ( 12 ) ;
1330- batch. apply_overlay ( Arc :: new ( overlay2) ) ;
1331- assert ! ( matches!( batch, BitmapBatch :: Layer ( _) ) ) ;
1332- }
13331322}
0 commit comments