@@ -279,146 +279,188 @@ where
279279
280280 /// Inserts `data` at its `height` within the chain.
281281 ///
282- /// The effect of `insert` depends on whether a height already exists. If it doesn't, the data
283- /// we inserted and all pre-existing entries higher than it will be re-inserted after it. If the
284- /// height already existed and has a conflicting block hash then it will be purged along with
285- /// all entries following it. If the existing checkpoint at height is a placeholder where
286- /// `data: None` with the same hash, then the `data` is inserted to make a complete checkpoint.
287- /// The returned chain will have a tip of the data passed in. If the data was already present
288- /// then this just returns `self`.
282+ /// This method maintains chain consistency by ensuring that all blocks are properly linked
283+ /// through their `prev_blockhash` relationships. When conflicts are detected, checkpoints
284+ /// are either displaced (converted to placeholders) or purged to maintain a valid chain.
289285 ///
290- /// When inserting data with a `prev_blockhash` that conflicts with existing checkpoints,
291- /// those checkpoints will be displaced and replaced with placeholders. When inserting data
292- /// whose block hash conflicts with the `prev_blockhash` of higher checkpoints, those higher
293- /// checkpoints will be purged.
286+ /// ## Behavior
287+ ///
288+ /// The insertion process follows these rules:
289+ ///
290+ /// 1. **If inserting at an existing height with the same hash:**
291+ /// - Placeholder checkpoint: Gets filled with the provided `data`
292+ /// - Complete checkpoint: Returns unchanged
293+ ///
294+ /// 2. **If inserting at an existing height with a different hash:**
295+ /// - The conflicting checkpoint and all above it are purged
296+ /// - The new data is inserted at that height
297+ ///
298+ /// 3. **If inserting at a new height:**
299+ /// - When `prev_blockhash` conflicts with the checkpoint below:
300+ /// - That checkpoint is displaced (converted to placeholder)
301+ /// - All checkpoints above are purged (they're now orphaned)
302+ /// - The new data is inserted, potentially becoming the new tip
303+ ///
304+ /// ## Examples
305+ ///
306+ /// ```text
307+ /// // Inserting with conflicting prev_blockhash
308+ /// Before: 98 -> 99 -> 100 -> 101
309+ /// Insert: block_100_new with prev=different_99
310+ /// After: 98 -> 99_placeholder -> 100_new
311+ /// (Note: 101 was purged as it was orphaned)
312+ /// ```
294313 ///
295314 /// # Panics
296315 ///
297316 /// This panics if called with a genesis block that differs from that of `self`.
298317 #[ must_use]
299318 pub fn insert ( self , height : u32 , data : D ) -> Self {
300- let mut cp = self . clone ( ) ;
301- let mut tail: Vec < ( u32 , D ) > = vec ! [ ] ;
302- let mut base = loop {
303- if cp. height ( ) == height {
304- let same_hash = cp. hash ( ) == data. to_blockhash ( ) ;
305- if same_hash {
306- if cp. data ( ) . is_some ( ) {
307- return self ;
308- } else {
309- // If `CheckPoint` is a placeholder, return previous `CheckPoint`.
310- break cp. prev ( ) . expect ( "can't be called on genesis block" ) ;
311- }
312- } else {
313- assert_ne ! ( cp. height( ) , 0 , "cannot replace genesis block" ) ;
314- // If we have a conflict we just return the inserted data because the tail is by
315- // implication invalid.
316- tail = vec ! [ ] ;
317- break cp. prev ( ) . expect ( "can't be called on genesis block" ) ;
319+ let hash = data. to_blockhash ( ) ;
320+ let data_id = BlockId { hash, height } ;
321+
322+ // Step 1: Split the chain into base (everything below height) and tail (height and above).
323+ // We collect the tail to re-insert after placing our new data.
324+ let mut base_opt = Some ( self . clone ( ) ) ;
325+ let mut tail = Vec :: < ( BlockId , D ) > :: new ( ) ;
326+ for ( cp_id, cp_data, prev) in self
327+ . iter ( )
328+ . filter ( |cp| cp. height ( ) != height)
329+ . take_while ( |cp| cp. height ( ) >= height + 1 )
330+ . filter_map ( |cp| Some ( ( cp. block_id ( ) , cp. data ( ) ?, cp. prev ( ) ) ) )
331+ {
332+ base_opt = prev;
333+ tail. push ( ( cp_id, cp_data) ) ;
334+ }
335+ let index = tail. partition_point ( |( id, _) | id. height > height) ;
336+ tail. insert ( index, ( data_id, data) ) ;
337+
338+ // Step 2: Rebuild the chain by pushing each element from tail onto base.
339+ // This process handles conflicts automatically through push's validation.
340+ while let Some ( ( tail_id, tail_data) ) = tail. pop ( ) {
341+ let base = match base_opt {
342+ Some ( base) => base,
343+ None => {
344+ base_opt = Some ( CheckPoint ( Arc :: new ( CPInner {
345+ block_id : tail_id,
346+ data : Some ( tail_data) ,
347+ prev : None ,
348+ } ) ) ) ;
349+ continue ;
318350 }
319- }
320-
321- if cp. height ( ) < height {
322- break cp;
323- }
351+ } ;
324352
325- if let Some ( d) = cp. data ( ) {
326- tail. push ( ( cp. height ( ) , d) ) ;
327- }
328- cp = cp. prev ( ) . expect ( "will break before genesis block" ) ;
329- } ;
330-
331- if let Some ( prev_hash) = data. prev_blockhash ( ) {
332- // Check if the new data's `prev_blockhash` conflicts with the checkpoint at height - 1.
333- if let Some ( lower_cp) = base. get ( height. saturating_sub ( 1 ) ) {
334- // New data's `prev_blockhash` conflicts with existing checkpoint, so we displace
335- // the existing checkpoint and create a placeholder.
336- if lower_cp. hash ( ) != prev_hash {
337- // Find the base to link to at height - 2 or lower with actual data.
338- // We skip placeholders because when we displace a checkpoint, we can't ensure
339- // that placeholders below it still maintain proper chain continuity.
340- let link_base = if height > 1 {
341- base. find_data ( height - 2 )
342- } else {
343- None
344- } ;
345-
346- // Create a new placeholder at height - 1 with the required `prev_blockhash`.
347- base = Self ( Arc :: new ( CPInner {
348- block_id : BlockId {
349- height : height - 1 ,
350- hash : prev_hash,
351- } ,
352- data : None ,
353- prev : link_base. map ( |cb| cb. 0 ) ,
354- } ) ) ;
355- }
356- } else {
357- // No checkpoint at height - 1, but we may need to create a placeholder.
358- if height > 0 {
359- base = Self ( Arc :: new ( CPInner {
360- block_id : BlockId {
361- height : height - 1 ,
362- hash : prev_hash,
363- } ,
364- data : None ,
365- prev : base. 0 . prev . clone ( ) ,
366- } ) ) ;
353+ match base. push ( tail_id. height , tail_data. clone ( ) ) {
354+ Ok ( cp) => {
355+ base_opt = Some ( cp) ;
356+ continue ;
367357 }
368- }
369- }
358+ Err ( cp) => {
359+ if tail_id. height == height {
360+ // Failed due to prev_blockhash conflict at height-1
361+ if cp. height ( ) + 1 == height {
362+ // Displace the conflicting checkpoint; clear tail as those are now orphaned
363+ base_opt = cp. prev ( ) ;
364+ tail. clear ( ) ;
365+ tail. push ( ( tail_id, tail_data) ) ;
366+ continue ;
367+ }
368+
369+ // Failed because height already exists
370+ if cp. height ( ) == height {
371+ base_opt = cp. prev ( ) ;
372+ if cp. hash ( ) != hash {
373+ // Hash conflicts: purge everything above
374+ tail. clear ( ) ;
375+ }
376+ // If hash matches, keep tail (everything above remains valid)
377+ tail. push ( ( tail_id, tail_data) ) ;
378+ continue ;
379+ }
380+ }
381+
382+ if tail_id. height > height {
383+ // Push failed for checkpoint above our insertion: this means the inserted
384+ // data broke the chain continuity (orphaned checkpoints), so we stop here
385+ base_opt = Some ( cp) ;
386+ break ;
387+ }
370388
371- // Check for conflicts with higher checkpoints and purge if necessary.
372- let mut filtered_tail = Vec :: new ( ) ;
373- for ( tail_height, tail_data) in tail. into_iter ( ) . rev ( ) {
374- // Check if this tail entry's `prev_blockhash` conflicts with our new data's blockhash.
375- if let Some ( tail_prev_hash) = tail_data. prev_blockhash ( ) {
376- // Conflict detected, so purge this and all tail entries.
377- if tail_prev_hash != data. to_blockhash ( ) {
378- break ;
389+ unreachable ! (
390+ "fail cannot be a result of pushing something that was part of `self`"
391+ ) ;
379392 }
380- }
381- filtered_tail. push ( ( tail_height, tail_data) ) ;
393+ } ;
382394 }
383395
384- base. extend ( core:: iter:: once ( ( height, data) ) . chain ( filtered_tail) )
385- . expect ( "tail is in order" )
396+ base_opt. expect ( "must have atleast one checkpoint" )
386397 }
387398
388- /// Extends the chain by pushing a new checkpoint.
399+ /// Extends the chain forward by pushing a new checkpoint.
400+ ///
401+ /// This method is for extending the chain with new checkpoints at heights greater than
402+ /// the current tip. It maintains chain continuity by creating placeholders when necessary.
403+ ///
404+ /// ## Behavior
405+ ///
406+ /// - **Height validation**: Only accepts heights greater than the current tip
407+ /// - **Chain continuity**: For non-contiguous heights with `prev_blockhash`, creates
408+ /// a placeholder at `height - 1` to maintain the chain link
409+ /// - **Conflict detection**: Fails if `prev_blockhash` doesn't match the expected parent
410+ /// - **Placeholder cleanup**: Removes any trailing placeholders before pushing
411+ ///
412+ /// ## Returns
413+ ///
414+ /// - `Ok(CheckPoint)`: Successfully extended chain with the new checkpoint as tip
415+ /// - `Err(self)`: Failed due to height ≤ current or `prev_blockhash` conflict
389416 ///
390- /// Returns `Err(self)` if the height is not greater than the current height, or if the data's
391- /// `prev_blockhash` conflicts with `self`.
417+ /// ## Example
392418 ///
393- /// Creates a placeholder at height - 1 if the height is non-contiguous and
394- /// `data.prev_blockhash()` is available.
395- pub fn push ( mut self , height : u32 , data : D ) -> Result < Self , Self > {
419+ /// ```text
420+ /// // Pushing non-contiguous height with prev_blockhash
421+ /// Before: 98 -> 99 -> 100
422+ /// Push: block_105 with prev=block_104
423+ /// After: 98 -> 99 -> 100 -> 104_placeholder -> 105
424+ /// ```
425+ pub fn push ( self , height : u32 , data : D ) -> Result < Self , Self > {
396426 // Reject if trying to push at or below current height - chain must grow forward
397427 if height <= self . height ( ) {
398428 return Err ( self ) ;
399429 }
400430
401- if let Some ( prev_hash) = data. prev_blockhash ( ) {
402- if height == self . height ( ) + 1 {
403- // For contiguous height, validate that prev_blockhash matches our hash
404- // to ensure chain continuity
405- if self . hash ( ) != prev_hash {
406- return Err ( self ) ;
431+ let mut prev = Some ( self . 0 . clone ( ) ) ;
432+
433+ // Remove any floating placeholder checkpoints.
434+ while let Some ( inner) = prev {
435+ if inner. data . is_some ( ) {
436+ prev = Some ( inner) ;
437+ break ;
438+ }
439+ prev = inner. prev . clone ( ) ;
440+ }
441+
442+ // Ensure we insert a placeholder if `prev.height` is not contiguous.
443+ if let ( Some ( prev_height) , Some ( prev_hash) ) = ( height. checked_sub ( 1 ) , data. prev_blockhash ( ) )
444+ {
445+ prev = match prev {
446+ Some ( inner) if inner. block_id . height == prev_height => {
447+ // For contiguous height, ensure prev_blockhash does not conflict.
448+ if inner. block_id . hash != prev_hash {
449+ return Err ( self ) ;
450+ }
451+ // No placeholder needed as chain has non-empty checkpoint already.
452+ Some ( inner)
407453 }
408- } else {
409- // For non-contiguous heights, create placeholder to maintain chain linkage
410- // This allows sparse chains while preserving block relationships
411- self = CheckPoint ( Arc :: new ( CPInner {
454+ // Insert placeholder for non-contiguous height.
455+ prev => Some ( Arc :: new ( CPInner {
412456 block_id : BlockId {
413- height : height
414- . checked_sub ( 1 )
415- . expect ( "height has previous blocks so must be greater than 0" ) ,
457+ height : prev_height,
416458 hash : prev_hash,
417459 } ,
418460 data : None ,
419- prev : Some ( self . 0 ) ,
420- } ) ) ;
421- }
461+ prev,
462+ } ) ) ,
463+ } ;
422464 }
423465
424466 Ok ( Self ( Arc :: new ( CPInner {
@@ -427,7 +469,7 @@ where
427469 hash : data. to_blockhash ( ) ,
428470 } ,
429471 data : Some ( data) ,
430- prev : Some ( self . 0 ) ,
472+ prev,
431473 } ) ) )
432474 }
433475}
0 commit comments