@@ -2,6 +2,7 @@ use core::fmt;
2
2
use core:: ops:: RangeBounds ;
3
3
4
4
use alloc:: sync:: Arc ;
5
+ use alloc:: vec:: Vec ;
5
6
use bitcoin:: { block:: Header , BlockHash } ;
6
7
7
8
use crate :: BlockId ;
@@ -284,16 +285,21 @@ where
284
285
/// all entries following it. If the existing checkpoint at height is a placeholder where
285
286
/// `data: None` with the same hash, then the `data` is inserted to make a complete checkpoint.
286
287
/// 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.
288
294
///
289
295
/// # Panics
290
296
///
291
297
/// This panics if called with a genesis block that differs from that of `self`.
292
298
#[ must_use]
293
299
pub fn insert ( self , height : u32 , data : D ) -> Self {
294
300
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 {
297
303
if cp. height ( ) == height {
298
304
let same_hash = cp. hash ( ) == data. to_blockhash ( ) ;
299
305
if same_hash {
@@ -322,47 +328,112 @@ where
322
328
cp = cp. prev ( ) . expect ( "will break before genesis block" ) ;
323
329
} ;
324
330
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
+ } ;
328
343
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 {
344
365
block_id : BlockId {
345
366
height : height - 1 ,
346
367
hash : prev_hash,
347
368
} ,
348
369
data : None ,
349
- prev : Some ( current_cp) ,
350
- } ) ;
351
- current_cp = empty;
370
+ prev : base. 0 . prev . clone ( ) ,
371
+ } ) ) ;
352
372
}
353
373
}
374
+ }
354
375
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
+ }
365
427
}
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
+ } ) ) )
366
437
}
367
438
}
368
439
0 commit comments