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