@@ -66,8 +66,8 @@ mod legacy;
6666pub struct ShardTree < S : ShardStore , const DEPTH : u8 , const SHARD_HEIGHT : u8 > {
6767 /// The vector of tree shards.
6868 store : S ,
69- /// The maximum number of checkpoints to retain before pruning.
70- max_checkpoints : usize ,
69+ /// The minumum number of checkpoints to retain when pruning.
70+ min_checkpoints_to_retain : usize ,
7171}
7272
7373impl <
@@ -79,10 +79,10 @@ impl<
7979 > ShardTree < S , DEPTH , SHARD_HEIGHT >
8080{
8181 /// Creates a new empty tree.
82- pub fn new ( store : S , max_checkpoints : usize ) -> Self {
82+ pub fn new ( store : S , min_checkpoints_to_retain : usize ) -> Self {
8383 Self {
8484 store,
85- max_checkpoints ,
85+ min_checkpoints_to_retain ,
8686 }
8787 }
8888
@@ -438,14 +438,19 @@ impl<
438438 . checkpoint_count ( )
439439 . map_err ( ShardTreeError :: Storage ) ?;
440440 trace ! (
441- "Tree has {} checkpoints, max is {}" ,
441+ "Tree has {} checkpoints, min to be retained is {}" ,
442442 checkpoint_count,
443- self . max_checkpoints ,
443+ self . min_checkpoints_to_retain ,
444444 ) ;
445- if checkpoint_count > self . max_checkpoints {
445+ let retain_count = self . min_checkpoints_to_retain
446+ + self
447+ . store
448+ . ensured_retained_count ( )
449+ . map_err ( ShardTreeError :: Storage ) ?;
450+ if checkpoint_count > retain_count {
446451 // Batch removals by subtree & create a list of the checkpoint identifiers that
447452 // will be removed from the checkpoints map.
448- let remove_count = checkpoint_count - self . max_checkpoints ;
453+ let remove_count = checkpoint_count - retain_count ;
449454 let mut checkpoints_to_delete = vec ! [ ] ;
450455 let mut clear_positions: BTreeMap < Address , BTreeMap < Position , RetentionFlags > > =
451456 BTreeMap :: new ( ) ;
@@ -454,8 +459,10 @@ impl<
454459 // When removing is true, we are iterating through the range of
455460 // checkpoints being removed. When remove is false, we are
456461 // iterating through the range of checkpoints that are being
457- // retained.
458- let removing = checkpoints_to_delete. len ( ) < remove_count;
462+ // retained, or skipping over a particular checkpoint that we
463+ // have been explicitly asked to retain.
464+ let removing = checkpoints_to_delete. len ( ) < remove_count
465+ && !self . store . should_retain ( cid) ?;
459466
460467 if removing {
461468 checkpoints_to_delete. push ( cid. clone ( ) ) ;
@@ -1177,9 +1184,9 @@ impl<
11771184 /// Make a marked leaf at a position eligible to be pruned.
11781185 ///
11791186 /// If the checkpoint associated with the specified identifier does not exist because the
1180- /// corresponding checkpoint would have been more than `max_checkpoints ` deep, the removal is
1181- /// recorded as of the first existing checkpoint and the associated leaves will be pruned when
1182- /// that checkpoint is subsequently removed.
1187+ /// corresponding checkpoint would have been more than `min_checkpoints_to_retain ` deep, the
1188+ /// removal is recorded as of the first existing checkpoint and the associated leaves will be
1189+ /// pruned when that checkpoint is subsequently removed.
11831190 ///
11841191 /// Returns `Ok(true)` if a mark was successfully removed from the leaf at the specified
11851192 /// position, `Ok(false)` if the tree does not contain a leaf at the specified position or is
@@ -1253,7 +1260,7 @@ mod tests {
12531260 } ;
12541261
12551262 use crate :: {
1256- store:: memory:: MemoryShardStore ,
1263+ store:: { memory:: MemoryShardStore , ShardStore } ,
12571264 testing:: {
12581265 arb_char_str, arb_shardtree, check_shard_sizes, check_shardtree_insertion,
12591266 check_witness_with_pruned_subtrees,
@@ -1355,21 +1362,57 @@ mod tests {
13551362 ) ,
13561363 Ok ( ( ) ) ,
13571364 ) ;
1365+
1366+ // Append a leaf we want to retain
1367+ assert_eq ! ( tree. append( 'e' . to_string( ) , Retention :: Marked ) , Ok ( ( ) ) , ) ;
1368+
1369+ // Now a few more leaves and then checkpoint
1370+ for c in 'f' ..='i' {
1371+ tree. append ( c. to_string ( ) , Retention :: Ephemeral ) . unwrap ( ) ;
1372+ }
1373+
1374+ // Checkpoint the tree. We'll want to retain this checkpoint.
1375+ assert_eq ! ( tree. checkpoint( 12 ) , Ok ( true ) ) ;
1376+ tree. store . ensure_retained ( 12 ) . unwrap ( ) ;
1377+
1378+ // Simulate adding yet another block
1379+ for c in 'j' ..='m' {
1380+ tree. append ( c. to_string ( ) , Retention :: Ephemeral ) . unwrap ( ) ;
1381+ }
1382+
1383+ assert_eq ! ( tree. checkpoint( 13 ) , Ok ( true ) ) ;
1384+
1385+ // Witness `e` as of checkpoint 12
1386+ let e_witness_12 = tree
1387+ . witness_at_checkpoint_id ( Position :: from ( 4 ) , & 12 )
1388+ . unwrap ( ) ;
1389+
1390+ // Now add some more checkpoints, which would ordinarily cause checkpoint 12
1391+ // to be pruned (but will not, because we explicitly retained it.)
1392+ for i in 14 ..24 {
1393+ assert_eq ! ( tree. checkpoint( i) , Ok ( true ) ) ;
1394+ }
1395+
1396+ // Verify that we can still compute the same root
1397+ assert_matches ! (
1398+ tree. witness_at_checkpoint_id( Position :: from( 4 ) , & 12 ) ,
1399+ Ok ( w) if w == e_witness_12
1400+ ) ;
13581401 }
13591402
13601403 // Combined tree tests
13611404 #[ allow( clippy:: type_complexity) ]
13621405 fn new_combined_tree < H : Hashable + Ord + Clone + core:: fmt:: Debug > (
1363- max_checkpoints : usize ,
1406+ min_checkpoints_to_retain : usize ,
13641407 ) -> CombinedTree <
13651408 H ,
13661409 usize ,
13671410 CompleteTree < H , usize , 4 > ,
13681411 ShardTree < MemoryShardStore < H , usize > , 4 , 3 > ,
13691412 > {
13701413 CombinedTree :: new (
1371- CompleteTree :: new ( max_checkpoints ) ,
1372- ShardTree :: new ( MemoryShardStore :: empty ( ) , max_checkpoints ) ,
1414+ CompleteTree :: new ( min_checkpoints_to_retain ) ,
1415+ ShardTree :: new ( MemoryShardStore :: empty ( ) , min_checkpoints_to_retain ) ,
13731416 )
13741417 }
13751418
0 commit comments