@@ -34,7 +34,7 @@ use kaspa_consensus_core::{
3434 BlockHashMap , BlockHashSet , BlockLevel ,
3535} ;
3636use kaspa_consensusmanager:: SessionLock ;
37- use kaspa_core:: { debug, info, warn} ;
37+ use kaspa_core:: { debug, info, time :: unix_now , trace , warn} ;
3838use kaspa_database:: prelude:: { BatchDbWriter , MemoryWriter , StoreResultExtensions , DB } ;
3939use kaspa_hashes:: Hash ;
4040use kaspa_muhash:: MuHash ;
@@ -134,6 +134,7 @@ impl PruningProcessor {
134134 let pruning_point_read = self . pruning_point_store . read ( ) ;
135135 let pruning_point = pruning_point_read. pruning_point ( ) . unwrap ( ) ;
136136 let history_root = pruning_point_read. history_root ( ) . unwrap_option ( ) ;
137+ let retention_period_root = pruning_point_read. retention_period_root ( ) . unwrap_or ( pruning_point) ;
137138 let pruning_utxoset_position = self . pruning_utxoset_stores . read ( ) . utxoset_position ( ) . unwrap_option ( ) ;
138139 drop ( pruning_point_read) ;
139140
@@ -153,11 +154,17 @@ impl PruningProcessor {
153154 }
154155 }
155156
157+ trace ! (
158+ "history_root: {:?} | retention_period_root: {} | pruning_point: {}" ,
159+ history_root,
160+ retention_period_root,
161+ pruning_point
162+ ) ;
156163 if let Some ( history_root) = history_root {
157164 // This indicates the node crashed or was forced to stop during a former data prune operation hence
158165 // we need to complete it
159- if history_root != pruning_point {
160- self . prune ( pruning_point) ;
166+ if history_root != retention_period_root {
167+ self . prune ( pruning_point, retention_period_root ) ;
161168 }
162169 }
163170
@@ -175,6 +182,15 @@ impl PruningProcessor {
175182 ) ;
176183
177184 if !new_pruning_points. is_empty ( ) {
185+ let retention_period_root = if let Ok ( retention_period_root) = pruning_point_read. retention_period_root ( ) {
186+ retention_period_root
187+ } else if let Ok ( history_root) = pruning_point_read. history_root ( ) {
188+ history_root
189+ } else {
190+ // pruning point always exist
191+ pruning_point_read. pruning_point ( ) . unwrap ( )
192+ } ;
193+
178194 // Update past pruning points and pruning point stores
179195 let mut batch = WriteBatch :: default ( ) ;
180196 let mut pruning_point_write = RwLockUpgradableReadGuard :: upgrade ( pruning_point_read) ;
@@ -183,10 +199,14 @@ impl PruningProcessor {
183199 }
184200 let new_pp_index = current_pruning_info. index + new_pruning_points. len ( ) as u64 ;
185201 let new_pruning_point = * new_pruning_points. last ( ) . unwrap ( ) ;
202+ let adjusted_retention_period_root = self . advance_retention_period_root ( retention_period_root, new_pruning_point) ;
186203 pruning_point_write. set_batch ( & mut batch, new_pruning_point, new_candidate, new_pp_index) . unwrap ( ) ;
204+ pruning_point_write. set_retention_period_root ( & mut batch, adjusted_retention_period_root) . unwrap ( ) ;
187205 self . db . write ( batch) . unwrap ( ) ;
188206 drop ( pruning_point_write) ;
189207
208+ trace ! ( "New Pruning Point: {} | New Retention Period Root: {}" , new_pruning_point, adjusted_retention_period_root) ;
209+
190210 // Inform the user
191211 info ! ( "Periodic pruning point movement: advancing from {} to {}" , current_pruning_info. pruning_point, new_pruning_point) ;
192212
@@ -198,7 +218,7 @@ impl PruningProcessor {
198218 info ! ( "Updated the pruning point UTXO set" ) ;
199219
200220 // Finally, prune data in the new pruning point past
201- self . prune ( new_pruning_point) ;
221+ self . prune ( new_pruning_point, adjusted_retention_period_root ) ;
202222 } else if new_candidate != current_pruning_info. candidate {
203223 let mut pruning_point_write = RwLockUpgradableReadGuard :: upgrade ( pruning_point_read) ;
204224 pruning_point_write. set ( current_pruning_info. pruning_point , new_candidate, current_pruning_info. index ) . unwrap ( ) ;
@@ -238,7 +258,7 @@ impl PruningProcessor {
238258 info ! ( "Pruning point UTXO commitment was verified correctly (sanity test)" ) ;
239259 }
240260
241- fn prune ( & self , new_pruning_point : Hash ) {
261+ fn prune ( & self , new_pruning_point : Hash , retention_period_root : Hash ) {
242262 if self . config . is_archival {
243263 warn ! ( "The node is configured as an archival node -- avoiding data pruning. Note this might lead to heavy disk usage." ) ;
244264 return ;
@@ -384,7 +404,7 @@ impl PruningProcessor {
384404 let ( mut counter, mut traversed) = ( 0 , 0 ) ;
385405 info ! ( "Header and Block pruning: starting traversal from: {} (genesis: {})" , queue. iter( ) . reusable_format( ", " ) , genesis) ;
386406 while let Some ( current) = queue. pop_front ( ) {
387- if reachability_read. is_dag_ancestor_of_result ( new_pruning_point , current) . unwrap ( ) {
407+ if reachability_read. is_dag_ancestor_of_result ( retention_period_root , current) . unwrap ( ) {
388408 continue ;
389409 }
390410 traversed += 1 ;
@@ -517,12 +537,47 @@ impl PruningProcessor {
517537 // Set the history root to the new pruning point only after we successfully pruned its past
518538 let mut pruning_point_write = self . pruning_point_store . write ( ) ;
519539 let mut batch = WriteBatch :: default ( ) ;
520- pruning_point_write. set_history_root ( & mut batch, new_pruning_point ) . unwrap ( ) ;
540+ pruning_point_write. set_history_root ( & mut batch, retention_period_root ) . unwrap ( ) ;
521541 self . db . write ( batch) . unwrap ( ) ;
522542 drop ( pruning_point_write) ;
523543 }
524544 }
525545
546+ /// Adjusts the retention period root forward until the maximum chain block is reached that covers the retention period.
547+ /// This is the last chain block B such that B.timestamp < retention_period_days_ago. This may return the old hash if
548+ /// the retention period cannot be covered yet with the node's current history.
549+ ///
550+ /// This function is expected to be called only when a new pruning point is determined and right before
551+ /// doing any pruning.
552+ ///
553+ /// retention_period_root is guaranteed to be in the past(pruning_point)
554+ fn advance_retention_period_root ( & self , retention_period_root : Hash , pruning_point : Hash ) -> Hash {
555+ // The retention period in milliseconds we need to cover
556+ let retention_period_ms = ( self . config . retention_period_days * 86400.0 * 1000.0 ) . ceil ( ) as u64 ;
557+
558+ let retention_period_root_ts_target = unix_now ( ) . saturating_sub ( retention_period_ms) ;
559+ let mut new_retention_period_root = retention_period_root;
560+
561+ trace ! (
562+ "Adjusting the retention period root to cover the required retention period. Target timestamp: {}" ,
563+ retention_period_root_ts_target,
564+ ) ;
565+
566+ for block in self . reachability_service . forward_chain_iterator ( retention_period_root, pruning_point, true ) {
567+ let timestamp = self . headers_store . get_compact_header_data ( block) . unwrap ( ) . timestamp ;
568+ trace ! ( "block | timestamp = {} | {}" , block, timestamp) ;
569+ if timestamp >= retention_period_root_ts_target {
570+ trace ! ( "block {} timestamp {} >= {}" , block, timestamp, retention_period_root_ts_target) ;
571+ // We are now at a chain block that is at or above our retention period target
572+ // Break here so we can return the hash from the previous iteration
573+ break ;
574+ }
575+ new_retention_period_root = block;
576+ }
577+
578+ new_retention_period_root
579+ }
580+
526581 fn past_pruning_points ( & self ) -> BlockHashSet {
527582 ( 0 ..self . pruning_point_store . read ( ) . get ( ) . unwrap ( ) . index )
528583 . map ( |index| self . past_pruning_points_store . get ( index) . unwrap ( ) )
0 commit comments