@@ -27,7 +27,10 @@ use crate::{
2727} ;
2828use commonware_codec:: Codec ;
2929use commonware_cryptography:: { Digest , Hasher } ;
30- use commonware_utils:: bitmap:: { Prunable as BitMap , Readable as BitmapReadable } ;
30+ use commonware_utils:: {
31+ bitmap:: { Prunable as BitMap , Readable as BitmapReadable } ,
32+ sync:: { RwLock , RwLockReadGuard , RwLockWriteGuard } ,
33+ } ;
3134use std:: {
3235 collections:: { BTreeMap , BTreeSet } ,
3336 sync:: Arc ,
@@ -279,6 +282,29 @@ where
279282///
280283/// Wraps an [`any::batch::MerkleizedBatch`] and adds the bitmap and grafted MMR state needed
281284/// to compute the canonical root.
285+ ///
286+ /// # Stale reads
287+ ///
288+ /// A `MerkleizedBatch` holds a reference to the DB's committed bitmap via
289+ /// [`BitmapBatch::Base`]. That bitmap evolves in place as [`Db::apply_batch`](super::db::Db)
290+ /// commits batches. Reads through this batch's chain (via its own `get`, by constructing child
291+ /// batches, or by anything that walks the bitmap chain) are only semantically correct if this
292+ /// batch's chain is still a valid prefix of the DB's committed state — i.e., every
293+ /// [`apply_batch`](super::db::Db::apply_batch) call since this batch was merkleized applied an
294+ /// ancestor of *this* batch.
295+ ///
296+ /// **The library does not guard against stale reads.** If you apply an unrelated sibling or any
297+ /// other non-ancestor batch and then read through this one, chunk reads fall through to the
298+ /// (now-advanced) committed bitmap while your overlays are still applied on top, producing
299+ /// incoherent state that doesn't correspond to any valid DB view.
300+ ///
301+ /// Rules of thumb:
302+ /// - Drop any `Arc<MerkleizedBatch>` you no longer intend to apply.
303+ /// - Extending a batch after `apply_batch` has consumed it (building a child off the
304+ /// just-applied parent) is safe — the committed bitmap now equals the parent's post-apply
305+ /// state, so child reads are consistent.
306+ /// - Extending a batch after a *different* branch has been applied is not safe and will
307+ /// silently return wrong data.
282308pub struct MerkleizedBatch < F : Graftable , D : Digest , U : update:: Update + Send + Sync , const N : usize >
283309where
284310 Operation < F , U > : Send + Sync ,
@@ -590,7 +616,6 @@ where
590616 // compute_db_root sees newly completed chunks. Using bitmap_parent alone would miss chunks
591617 // that transitioned from partial to complete in this batch.
592618 let bitmap_batch = BitmapBatch :: Layer ( Arc :: new ( BitmapBatchLayer {
593- pruned_chunks : bitmap_parent. pruned_chunks ( ) ,
594619 parent : bitmap_parent. clone ( ) ,
595620 overlay : Arc :: new ( overlay) ,
596621 } ) ) ;
@@ -632,13 +657,86 @@ where
632657 } ) )
633658}
634659
635- /// Immutable bitmap state at any point in a batch chain.
660+ /// The committed bitmap shared between the [`Db`](super::db::Db) and live batches.
661+ ///
662+ /// Wrapped in a [`RwLock`] so that [`Db::apply_batch`](super::db::Db::apply_batch),
663+ /// [`Db::prune`](super::db::Db::prune), and [`Db::rewind`](super::db::Db::rewind) can mutate
664+ /// the bitmap in place while live batches concurrently read through it. Batches never write;
665+ /// write access is kept module-private to `current::db`.
666+ ///
667+ /// # Stale reads
668+ ///
669+ /// The bitmap behind this lock represents *committed* state. If a caller holds a `MerkleizedBatch`
670+ /// whose chain is no longer a valid prefix of committed state (e.g., an orphaned sibling branch
671+ /// after the other branch was applied), reads through that chain's `Base` will silently return
672+ /// inconsistent data (the chain's overlays mixed with post-divergence committed chunks). The
673+ /// library does not guard against this; callers must avoid reading through stale batches.
674+ pub ( crate ) struct SharedBitmap < const N : usize > {
675+ inner : RwLock < BitMap < N > > ,
676+ }
677+
678+ impl < const N : usize > SharedBitmap < N > {
679+ pub ( crate ) const fn new ( bitmap : BitMap < N > ) -> Self {
680+ Self {
681+ inner : RwLock :: new ( bitmap) ,
682+ }
683+ }
684+
685+ /// Acquire a shared read guard over the committed bitmap.
686+ pub ( crate ) fn read ( & self ) -> RwLockReadGuard < ' _ , BitMap < N > > {
687+ self . inner . read ( )
688+ }
689+
690+ /// Acquire an exclusive write guard. Only [`Db::apply_batch`](super::db::Db::apply_batch),
691+ /// [`Db::prune`](super::db::Db::prune), and [`Db::rewind`](super::db::Db::rewind) mutate the
692+ /// shared bitmap.
693+ pub ( super ) fn write ( & self ) -> RwLockWriteGuard < ' _ , BitMap < N > > {
694+ self . inner . write ( )
695+ }
696+ }
697+
698+ impl < const N : usize > std:: fmt:: Debug for SharedBitmap < N > {
699+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
700+ f. debug_struct ( "SharedBitmap" )
701+ . field ( "bitmap_len" , & BitmapReadable :: < N > :: len ( & * self . read ( ) ) )
702+ . finish ( )
703+ }
704+ }
705+
706+ /// [`BitmapReadable`] over the DB's committed bitmap. Each call acquires the read lock briefly.
707+ impl < const N : usize > BitmapReadable < N > for SharedBitmap < N > {
708+ fn complete_chunks ( & self ) -> usize {
709+ self . read ( ) . complete_chunks ( )
710+ }
711+
712+ fn get_chunk ( & self , idx : usize ) -> [ u8 ; N ] {
713+ * self . read ( ) . get_chunk ( idx)
714+ }
715+
716+ fn last_chunk ( & self ) -> ( [ u8 ; N ] , u64 ) {
717+ let guard = self . read ( ) ;
718+ let ( chunk, bits) = guard. last_chunk ( ) ;
719+ ( * chunk, bits)
720+ }
721+
722+ fn pruned_chunks ( & self ) -> usize {
723+ self . read ( ) . pruned_chunks ( )
724+ }
725+
726+ fn len ( & self ) -> u64 {
727+ BitmapReadable :: < N > :: len ( & * self . read ( ) )
728+ }
729+ }
730+
731+ /// A view of the committed bitmap plus zero or more speculative overlay [`Layer`]s.
636732///
637- /// Mirrors the [`crate::merkle::batch::MerkleizedBatch`] pattern.
733+ /// The chain terminates in a [`Base`] that references the shared committed bitmap. No staleness
734+ /// check is performed — callers must ensure they only read through batches whose chains are
735+ /// still valid prefixes of committed state (see [`SharedBitmap`]'s docs).
638736#[ derive( Clone , Debug ) ]
639737pub ( crate ) enum BitmapBatch < const N : usize > {
640- /// Committed bitmap (chain terminal) .
641- Base ( Arc < BitMap < N > > ) ,
738+ /// Chain terminal: shared reference to the committed bitmap .
739+ Base ( Arc < SharedBitmap < N > > ) ,
642740 /// Speculative layer on top of a parent batch.
643741 Layer ( Arc < BitmapBatchLayer < N > > ) ,
644742}
@@ -649,10 +747,6 @@ pub(crate) struct BitmapBatchLayer<const N: usize> {
649747 pub ( crate ) parent : BitmapBatch < N > ,
650748 /// Chunk-level overlay: materialized bytes for every chunk that differs from parent.
651749 pub ( crate ) overlay : Arc < ChunkOverlay < N > > ,
652- /// Pruned-chunk count, copied from `parent` at construction. Invariant across the whole
653- /// layer chain (pruning only happens on the committed base), so caching here lets
654- /// `BitmapBatch::pruned_chunks` return in O(1) instead of walking to the Base.
655- pub ( crate ) pruned_chunks : usize ,
656750}
657751
658752impl < const N : usize > BitmapBatch < N > {
@@ -666,7 +760,7 @@ impl<const N: usize> BitmapReadable<N> for BitmapBatch<N> {
666760
667761 fn get_chunk ( & self , idx : usize ) -> [ u8 ; N ] {
668762 match self {
669- Self :: Base ( bm ) => * bm . get_chunk ( idx) ,
763+ Self :: Base ( shared ) => * shared . read ( ) . get_chunk ( idx) ,
670764 Self :: Layer ( layer) => {
671765 // Check overlay first; fall through to parent if unmodified.
672766 if let Some ( & chunk) = layer. overlay . get ( idx) {
@@ -695,95 +789,19 @@ impl<const N: usize> BitmapReadable<N> for BitmapBatch<N> {
695789
696790 fn pruned_chunks ( & self ) -> usize {
697791 match self {
698- Self :: Base ( bm ) => bm . pruned_chunks ( ) ,
699- Self :: Layer ( layer) => layer. pruned_chunks ,
792+ Self :: Base ( shared ) => shared . read ( ) . pruned_chunks ( ) ,
793+ Self :: Layer ( layer) => layer. parent . pruned_chunks ( ) ,
700794 }
701795 }
702796
703797 fn len ( & self ) -> u64 {
704798 match self {
705- Self :: Base ( bm ) => BitmapReadable :: < N > :: len ( bm . as_ref ( ) ) ,
799+ Self :: Base ( shared ) => BitmapReadable :: < N > :: len ( & * shared . read ( ) ) ,
706800 Self :: Layer ( layer) => layer. overlay . len ,
707801 }
708802 }
709803}
710804
711- impl < const N : usize > BitmapBatch < N > {
712- /// Apply a chunk overlay to this bitmap. When `self` is `Base` with sole ownership, writes
713- /// overlay chunks directly into the bitmap. Otherwise creates a new `Layer`.
714- pub ( super ) fn apply_overlay ( & mut self , overlay : Arc < ChunkOverlay < N > > ) {
715- // Fast path: write overlay chunks directly into the Base bitmap.
716- if let Self :: Base ( base) = self {
717- if let Some ( bitmap) = Arc :: get_mut ( base) {
718- // Extend bitmap to the overlay's length.
719- bitmap. extend_to ( overlay. len ) ;
720- // Overwrite dirty chunks.
721- for ( & idx, chunk_bytes) in & overlay. chunks {
722- if idx >= bitmap. pruned_chunks ( ) {
723- bitmap. set_chunk_by_index ( idx, chunk_bytes) ;
724- }
725- }
726- return ;
727- }
728- }
729-
730- // Slow path: create a new layer.
731- let pruned_chunks = self . pruned_chunks ( ) ;
732- let parent = self . clone ( ) ;
733- * self = Self :: Layer ( Arc :: new ( BitmapBatchLayer {
734- parent,
735- overlay,
736- pruned_chunks,
737- } ) ) ;
738- }
739-
740- /// Flatten all layers back to a single `Base(Arc<BitMap<N>>)`.
741- ///
742- /// After flattening, the new `Base` Arc has refcount 1 (assuming no external clones
743- /// are held).
744- pub ( super ) fn flatten ( & mut self ) {
745- if matches ! ( self , Self :: Base ( _) ) {
746- return ;
747- }
748-
749- // Take ownership of the chain so that Arc refcounts are not
750- // artificially inflated by a clone.
751- let mut owned = std:: mem:: replace ( self , Self :: Base ( Arc :: new ( BitMap :: default ( ) ) ) ) ;
752-
753- // Collect overlays from tip to base.
754- let mut overlays = Vec :: new ( ) ;
755- let base = loop {
756- match owned {
757- Self :: Base ( bm) => break bm,
758- Self :: Layer ( layer) => match Arc :: try_unwrap ( layer) {
759- Ok ( inner) => {
760- overlays. push ( inner. overlay ) ;
761- owned = inner. parent ;
762- }
763- Err ( arc) => {
764- overlays. push ( arc. overlay . clone ( ) ) ;
765- owned = arc. parent . clone ( ) ;
766- }
767- } ,
768- }
769- } ;
770-
771- // Apply overlays from base to tip.
772- let mut bitmap = Arc :: try_unwrap ( base) . unwrap_or_else ( |arc| ( * arc) . clone ( ) ) ;
773- for overlay in overlays. into_iter ( ) . rev ( ) {
774- // Extend bitmap to the overlay's length.
775- bitmap. extend_to ( overlay. len ) ;
776- // Apply dirty chunks.
777- for ( & idx, chunk_bytes) in & overlay. chunks {
778- if idx >= bitmap. pruned_chunks ( ) {
779- bitmap. set_chunk_by_index ( idx, chunk_bytes) ;
780- }
781- }
782- }
783- * self = Self :: Base ( Arc :: new ( bitmap) ) ;
784- }
785- }
786-
787805impl < F : Graftable , D : Digest , U : update:: Update + Send + Sync , const N : usize >
788806 MerkleizedBatch < F , D , U , N >
789807where
@@ -853,7 +871,7 @@ where
853871 Arc :: new ( MerkleizedBatch {
854872 inner : self . any . to_batch ( ) ,
855873 grafted,
856- bitmap : self . status . clone ( ) ,
874+ bitmap : BitmapBatch :: Base ( Arc :: clone ( & self . status ) ) ,
857875 canonical_root : self . root ,
858876 } )
859877 }
@@ -1294,32 +1312,4 @@ mod tests {
12941312 assert_eq ! ( scan. next_candidate( Location :: new( 0 ) , 0 ) , None ) ;
12951313 }
12961314
1297- #[ test]
1298- fn test_apply_overlay ( ) {
1299- // Base: 8 bits all set, sole owner -> fast path.
1300- let base = make_bitmap ( & [ true ; 8 ] ) ;
1301- let mut batch = BitmapBatch :: Base ( Arc :: new ( base) ) ;
1302-
1303- let mut overlay = ChunkOverlay :: new ( 12 ) ;
1304- let mut c0 = [ 0u8 ; N ] ;
1305- c0[ 0 ] = 0b1111_0111 ; // bits 0-7 set except bit 3
1306- c0[ 1 ] = 0b0000_0100 ; // bit 10 set
1307- overlay. chunks . insert ( 0 , c0) ;
1308- batch. apply_overlay ( Arc :: new ( overlay) ) ;
1309-
1310- // Fast path keeps Base, extends length, applies chunks.
1311- assert ! ( matches!( batch, BitmapBatch :: Base ( _) ) ) ;
1312- assert_eq ! ( batch. len( ) , 12 ) ;
1313- assert_eq ! ( batch. get_chunk( 0 ) [ 0 ] & ( 1 << 3 ) , 0 ) ;
1314- assert_ne ! ( batch. get_chunk( 0 ) [ 1 ] & ( 1 << 2 ) , 0 ) ;
1315-
1316- // Shared Arc -> slow path creates Layer.
1317- let BitmapBatch :: Base ( ref base_arc) = batch else {
1318- panic ! ( "expected Base" ) ;
1319- } ;
1320- let _shared = Arc :: clone ( base_arc) ;
1321- let overlay2 = ChunkOverlay :: new ( 12 ) ;
1322- batch. apply_overlay ( Arc :: new ( overlay2) ) ;
1323- assert ! ( matches!( batch, BitmapBatch :: Layer ( _) ) ) ;
1324- }
13251315}
0 commit comments