@@ -2,6 +2,7 @@ use core::fmt;
22use core:: ops:: RangeBounds ;
33
44use alloc:: sync:: Arc ;
5+ use alloc:: vec:: Vec ;
56use bitcoin:: { block:: Header , BlockHash } ;
67
78use crate :: BlockId ;
@@ -280,16 +281,21 @@ where
280281 /// all entries following it. If the existing checkpoint at height is a placeholder where
281282 /// `data: None` with the same hash, then the `data` is inserted to make a complete checkpoint.
282283 /// The returned chain will have a tip of the data passed in. If the data was already present
283- /// then this just returns `self`. This method does not create new placeholders.
284+ /// then this just returns `self`.
285+ ///
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.
284290 ///
285291 /// # Panics
286292 ///
287293 /// This panics if called with a genesis block that differs from that of `self`.
288294 #[ must_use]
289295 pub fn insert ( self , height : u32 , data : D ) -> Self {
290296 let mut cp = self . clone ( ) ;
291- let mut tail = vec ! [ ] ;
292- let base = loop {
297+ let mut tail: Vec < ( u32 , D ) > = vec ! [ ] ;
298+ let mut base = loop {
293299 if cp. height ( ) == height {
294300 let same_hash = cp. hash ( ) == data. to_blockhash ( ) ;
295301 if same_hash {
@@ -318,47 +324,107 @@ where
318324 cp = cp. prev ( ) . expect ( "will break before genesis block" ) ;
319325 } ;
320326
321- base. extend ( core:: iter:: once ( ( height, data) ) . chain ( tail. into_iter ( ) . rev ( ) ) )
322- . expect ( "tail is in order" )
323- }
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+ } ;
324341
325- /// Puts another checkpoint onto the linked list representing the blockchain.
326- ///
327- /// Returns an `Err(self)` if the block you are pushing on is not at a greater height that the
328- /// one you are pushing on to.
329- ///
330- /// If `height` is non-contiguous and `data.prev_blockhash()` is available, a placeholder is
331- /// created at height - 1.
332- pub fn push ( self , height : u32 , data : D ) -> Result < Self , Self > {
333- if self . height ( ) < height {
334- let mut current_cp = self . 0 . clone ( ) ;
335-
336- // If non-contiguous and `prev_blockhash` exists, insert a placeholder at height - 1.
337- if height > self . height ( ) + 1 {
338- if let Some ( prev_hash) = data. prev_blockhash ( ) {
339- let empty = Arc :: new ( CPInner {
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 {
340356 block_id : BlockId {
341357 height : height - 1 ,
342358 hash : prev_hash,
343359 } ,
344360 data : None ,
345- prev : Some ( current_cp) ,
346- } ) ;
347- current_cp = empty;
361+ prev : base. 0 . prev . clone ( ) ,
362+ } ) ) ;
348363 }
349364 }
365+ }
350366
351- Ok ( Self ( Arc :: new ( CPInner {
352- block_id : BlockId {
353- height,
354- hash : data. to_blockhash ( ) ,
355- } ,
356- data : Some ( data) ,
357- prev : Some ( current_cp) ,
358- } ) ) )
359- } else {
360- Err ( self )
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 ;
375+ }
376+ }
377+ filtered_tail. push ( ( tail_height, tail_data) ) ;
378+ }
379+
380+ base. extend ( core:: iter:: once ( ( height, data) ) . chain ( filtered_tail) )
381+ . expect ( "tail is in order" )
382+ }
383+
384+ /// Extends the chain by pushing a new checkpoint.
385+ ///
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`.
388+ ///
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 > {
392+ // Reject if trying to push at or below current height - chain must grow forward
393+ if height <= self . height ( ) {
394+ return Err ( self ) ;
395+ }
396+
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 ) ;
403+ }
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 {
408+ block_id : BlockId {
409+ height : height
410+ . checked_sub ( 1 )
411+ . expect ( "height has previous blocks so must be greater than 0" ) ,
412+ hash : prev_hash,
413+ } ,
414+ data : None ,
415+ prev : Some ( self . 0 ) ,
416+ } ) ) ;
417+ }
361418 }
419+
420+ Ok ( Self ( Arc :: new ( CPInner {
421+ block_id : BlockId {
422+ height,
423+ hash : data. to_blockhash ( ) ,
424+ } ,
425+ data : Some ( data) ,
426+ prev : Some ( self . 0 ) ,
427+ } ) ) )
362428 }
363429}
364430
0 commit comments