@@ -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 ;
@@ -284,16 +285,21 @@ where
284285 /// all entries following it. If the existing checkpoint at height is a placeholder where
285286 /// `data: None` with the same hash, then the `data` is inserted to make a complete checkpoint.
286287 /// The returned chain will have a tip of the data passed in. If the data was already present
287- /// then this just returns `self`. This method does not create new placeholders.
288+ /// then this just returns `self`.
289+ ///
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.
288294 ///
289295 /// # Panics
290296 ///
291297 /// This panics if called with a genesis block that differs from that of `self`.
292298 #[ must_use]
293299 pub fn insert ( self , height : u32 , data : D ) -> Self {
294300 let mut cp = self . clone ( ) ;
295- let mut tail = vec ! [ ] ;
296- let base = loop {
301+ let mut tail: Vec < ( u32 , D ) > = vec ! [ ] ;
302+ let mut base = loop {
297303 if cp. height ( ) == height {
298304 let same_hash = cp. hash ( ) == data. to_blockhash ( ) ;
299305 if same_hash {
@@ -322,47 +328,112 @@ where
322328 cp = cp. prev ( ) . expect ( "will break before genesis block" ) ;
323329 } ;
324330
325- base. extend ( core:: iter:: once ( ( height, data) ) . chain ( tail. into_iter ( ) . rev ( ) ) )
326- . expect ( "tail is in order" )
327- }
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.
338+ let link_base = if height > 1 {
339+ base. floor_at ( height - 2 )
340+ } else {
341+ None
342+ } ;
328343
329- /// Puts another checkpoint onto the linked list representing the blockchain.
330- ///
331- /// Returns an `Err(self)` if the block you are pushing on is not at a greater height that the
332- /// one you are pushing on to.
333- ///
334- /// If `height` is non-contiguous and `data.prev_blockhash()` is available, a placeholder is
335- /// created at height - 1.
336- pub fn push ( self , height : u32 , data : D ) -> Result < Self , Self > {
337- if self . height ( ) < height {
338- let mut current_cp = self . 0 . clone ( ) ;
339-
340- // If non-contiguous and `prev_blockhash` exists, insert a placeholder at height - 1.
341- if height > self . height ( ) + 1 {
342- if let Some ( prev_hash) = data. prev_blockhash ( ) {
343- let empty = Arc :: new ( CPInner {
344+ // Create a new placeholder at height - 1 with the required `prev_blockhash`.
345+ base = Self ( Arc :: new ( CPInner {
346+ block_id : BlockId {
347+ height : height - 1 ,
348+ hash : prev_hash,
349+ } ,
350+ data : None ,
351+ prev : link_base. map ( |cb| cb. 0 ) . or_else ( || {
352+ // Keep any existing checkpoints below height - 2.
353+ if height > 1 {
354+ base. prev ( ) . map ( |p| p. 0 )
355+ } else {
356+ None
357+ }
358+ } ) ,
359+ } ) ) ;
360+ }
361+ } else {
362+ // No checkpoint at height - 1, but we may need to create a placeholder.
363+ if height > 0 {
364+ base = Self ( Arc :: new ( CPInner {
344365 block_id : BlockId {
345366 height : height - 1 ,
346367 hash : prev_hash,
347368 } ,
348369 data : None ,
349- prev : Some ( current_cp) ,
350- } ) ;
351- current_cp = empty;
370+ prev : base. 0 . prev . clone ( ) ,
371+ } ) ) ;
352372 }
353373 }
374+ }
354375
355- Ok ( Self ( Arc :: new ( CPInner {
356- block_id : BlockId {
357- height,
358- hash : data. to_blockhash ( ) ,
359- } ,
360- data : Some ( data) ,
361- prev : Some ( current_cp) ,
362- } ) ) )
363- } else {
364- Err ( self )
376+ // Check for conflicts with higher checkpoints and purge if necessary.
377+ let mut filtered_tail = Vec :: new ( ) ;
378+ for ( tail_height, tail_data) in tail. into_iter ( ) . rev ( ) {
379+ // Check if this tail entry's `prev_blockhash` conflicts with our new data's blockhash.
380+ if let Some ( tail_prev_hash) = tail_data. prev_blockhash ( ) {
381+ // Conflict detected, so purge this and all tail entries.
382+ if tail_prev_hash != data. to_blockhash ( ) {
383+ break ;
384+ }
385+ }
386+ filtered_tail. push ( ( tail_height, tail_data) ) ;
387+ }
388+
389+ base. extend ( core:: iter:: once ( ( height, data) ) . chain ( filtered_tail) )
390+ . expect ( "tail is in order" )
391+ }
392+
393+ /// Extends the chain by pushing a new checkpoint.
394+ ///
395+ /// Returns `Err(self)` if the height is not greater than the current height, or if the data's
396+ /// `prev_blockhash` conflicts with `self`.
397+ ///
398+ /// Creates a placeholder at height - 1 if the height is non-contiguous and
399+ /// `data.prev_blockhash()` is available.
400+ pub fn push ( mut self , height : u32 , data : D ) -> Result < Self , Self > {
401+ // Reject if trying to push at or below current height - chain must grow forward
402+ if height <= self . height ( ) {
403+ return Err ( self ) ;
404+ }
405+
406+ if let Some ( prev_hash) = data. prev_blockhash ( ) {
407+ if height == self . height ( ) + 1 {
408+ // For contiguous height, validate that prev_blockhash matches our hash
409+ // to ensure chain continuity
410+ if self . hash ( ) != prev_hash {
411+ return Err ( self ) ;
412+ }
413+ } else {
414+ // For non-contiguous heights, create placeholder to maintain chain linkage
415+ // This allows sparse chains while preserving block relationships
416+ self = CheckPoint ( Arc :: new ( CPInner {
417+ block_id : BlockId {
418+ height : height
419+ . checked_sub ( 1 )
420+ . expect ( "height has previous blocks so must be greater than 0" ) ,
421+ hash : prev_hash,
422+ } ,
423+ data : None ,
424+ prev : Some ( self . 0 ) ,
425+ } ) ) ;
426+ }
365427 }
428+
429+ Ok ( Self ( Arc :: new ( CPInner {
430+ block_id : BlockId {
431+ height,
432+ hash : data. to_blockhash ( ) ,
433+ } ,
434+ data : Some ( data) ,
435+ prev : Some ( self . 0 ) ,
436+ } ) ) )
366437 }
367438}
368439
0 commit comments