Skip to content

Commit ba55b7e

Browse files
committed
fix(core): fix CheckPoint::insert logic
1 parent a0cb974 commit ba55b7e

File tree

1 file changed

+80
-5
lines changed

1 file changed

+80
-5
lines changed

crates/core/src/checkpoint.rs

Lines changed: 80 additions & 5 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,19 +328,88 @@ 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()))
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+
};
343+
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 {
365+
block_id: BlockId {
366+
height: height - 1,
367+
hash: prev_hash,
368+
},
369+
data: None,
370+
prev: base.0.prev.clone(),
371+
}));
372+
}
373+
}
374+
}
375+
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))
326390
.expect("tail is in order")
327391
}
328392

329393
/// Puts another checkpoint onto the linked list representing the blockchain.
330394
///
331395
/// 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.
396+
/// one you are pushing on to, or if the `prev_blockhash` conflicts with the current tip's hash
397+
/// for contiguous heights.
333398
///
334399
/// If `height` is non-contiguous and `data.prev_blockhash()` is available, a placeholder is
335400
/// created at height - 1.
336401
pub fn push(self, height: u32, data: D) -> Result<Self, Self> {
337402
if self.height() < height {
403+
// For contiguous heights, validate prev_blockhash if available
404+
if height == self.height() + 1 {
405+
if let Some(prev_hash) = data.prev_blockhash() {
406+
if prev_hash != self.hash() {
407+
// Conflict: prev_blockhash doesn't match current tip's hash
408+
return Err(self);
409+
}
410+
}
411+
}
412+
338413
let mut current_cp = self.0.clone();
339414

340415
// If non-contiguous and `prev_blockhash` exists, insert a placeholder at height - 1.

0 commit comments

Comments
 (0)