Skip to content

Commit a216494

Browse files
committed
fix(core): fix CheckPoint::insert and CheckPoint::push logic
1 parent a0cb974 commit a216494

File tree

1 file changed

+105
-34
lines changed

1 file changed

+105
-34
lines changed

crates/core/src/checkpoint.rs

Lines changed: 105 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use core::fmt;
22
use core::ops::RangeBounds;
33

44
use alloc::sync::Arc;
5+
use alloc::vec::Vec;
56
use bitcoin::{block::Header, BlockHash};
67

78
use 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

Comments
 (0)