@@ -68,6 +68,8 @@ pub struct ShardTree<S: ShardStore, const DEPTH: u8, const SHARD_HEIGHT: u8> {
6868 store : S ,
6969 /// The maximum number of checkpoints to retain before pruning.
7070 max_checkpoints : usize ,
71+ /// The set of checkpoints to be explicitly retained in pruning operations.
72+ to_retain : BTreeSet < S :: CheckpointId >
7173}
7274
7375impl <
8385 Self {
8486 store,
8587 max_checkpoints,
88+ to_retain : BTreeSet :: new ( )
8689 }
8790 }
8891
@@ -111,6 +114,12 @@ impl<
111114 ( 0x1 << ( DEPTH - SHARD_HEIGHT ) ) - 1
112115 }
113116
117+ /// Adds the provided checkpoint to the set of checkpoints to be retained
118+ /// across pruning operations.
119+ pub fn ensure_retained ( & mut self , checkpoint_id : C ) {
120+ self . to_retain . insert ( checkpoint_id) ;
121+ }
122+
114123 /// Returns the leaf value at the specified position, if it is a marked leaf.
115124 pub fn get_marked_leaf (
116125 & self ,
@@ -442,10 +451,11 @@ impl<
442451 checkpoint_count,
443452 self . max_checkpoints,
444453 ) ;
445- if checkpoint_count > self . max_checkpoints {
454+ let retain_count = self . max_checkpoints + self . to_retain . len ( ) ;
455+ if checkpoint_count > retain_count {
446456 // Batch removals by subtree & create a list of the checkpoint identifiers that
447457 // will be removed from the checkpoints map.
448- let remove_count = checkpoint_count - self . max_checkpoints ;
458+ let remove_count = checkpoint_count - retain_count ;
449459 let mut checkpoints_to_delete = vec ! [ ] ;
450460 let mut clear_positions: BTreeMap < Address , BTreeMap < Position , RetentionFlags > > =
451461 BTreeMap :: new ( ) ;
@@ -454,8 +464,9 @@ impl<
454464 // When removing is true, we are iterating through the range of
455465 // checkpoints being removed. When remove is false, we are
456466 // iterating through the range of checkpoints that are being
457- // retained.
458- let removing = checkpoints_to_delete. len ( ) < remove_count;
467+ // retained, or skipping over a particular checkpoint that we
468+ // have been explicitly asked to retain.
469+ let removing = checkpoints_to_delete. len ( ) < remove_count && !self . to_retain . contains ( cid) ;
459470
460471 if removing {
461472 checkpoints_to_delete. push ( cid. clone ( ) ) ;
@@ -1355,6 +1366,43 @@ mod tests {
13551366 ) ,
13561367 Ok ( ( ) ) ,
13571368 ) ;
1369+
1370+ // Append a leaf we want to retain
1371+ assert_eq ! (
1372+ tree. append( 'e' . to_string( ) , Retention :: Marked ) ,
1373+ Ok ( ( ) ) ,
1374+ ) ;
1375+
1376+ // Now a few more leaves and then checkpoint
1377+ for c in 'f' ..='i' {
1378+ tree. append ( c. to_string ( ) , Retention :: Ephemeral ) . unwrap ( ) ;
1379+ }
1380+
1381+ // Checkpoint the tree. We'll want to retain this checkpoint.
1382+ assert_eq ! ( tree. checkpoint( 12 ) , Ok ( true ) ) ;
1383+ tree. ensure_retained ( 12 ) ;
1384+
1385+ // Simulate adding yet another block
1386+ for c in 'j' ..='m' {
1387+ tree. append ( c. to_string ( ) , Retention :: Ephemeral ) . unwrap ( ) ;
1388+ }
1389+
1390+ assert_eq ! ( tree. checkpoint( 13 ) , Ok ( true ) ) ;
1391+
1392+ // Witness `e` as of checkpoint 12
1393+ let e_witness_12 = tree. witness_at_checkpoint_id ( Position :: from ( 4 ) , & 12 ) . unwrap ( ) ;
1394+
1395+ // Now add some more checkpoints, which would ordinarily cause checkpoint 12
1396+ // to be pruned (but will not, because we explicitly retained it.)
1397+ for i in 14 ..24 {
1398+ assert_eq ! ( tree. checkpoint( i) , Ok ( true ) ) ;
1399+ }
1400+
1401+ // Verify that we can still compute the same root
1402+ assert_matches ! (
1403+ tree. witness_at_checkpoint_id( Position :: from( 4 ) , & 12 ) ,
1404+ Ok ( w) if w == e_witness_12
1405+ ) ;
13581406 }
13591407
13601408 // Combined tree tests
0 commit comments