Skip to content

Commit a2a8630

Browse files
evanlinjinclaude
andcommitted
feat(core): add skiplist to CheckPoint for O(√n) traversal
Add skip pointers and index tracking to CheckPoint structure with CHECKPOINT_SKIP_INTERVAL=100. Update get(), floor_at(), range(), insert() and push() methods to leverage skip pointers. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent faf520d commit a2a8630

File tree

1 file changed

+210
-10
lines changed

1 file changed

+210
-10
lines changed

crates/core/src/checkpoint.rs

Lines changed: 210 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ use bitcoin::{block::Header, BlockHash};
66

77
use crate::BlockId;
88

9+
/// Interval for skiplist pointers based on checkpoint index.
10+
const CHECKPOINT_SKIP_INTERVAL: u32 = 100;
11+
912
/// A checkpoint is a node of a reference-counted linked list of [`BlockId`]s.
1013
///
1114
/// Checkpoints are cheaply cloneable and are useful to find the agreement point between two sparse
@@ -28,6 +31,10 @@ struct CPInner<D> {
2831
data: D,
2932
/// Previous checkpoint (if any).
3033
prev: Option<Arc<CPInner<D>>>,
34+
/// Skip pointer for fast traversals.
35+
skip: Option<Arc<CPInner<D>>>,
36+
/// Index of this checkpoint (number of checkpoints from the first).
37+
index: u32,
3138
}
3239

3340
/// When a `CPInner` is dropped we need to go back down the chain and manually remove any
@@ -125,6 +132,16 @@ impl<D> CheckPoint<D> {
125132
self.0.prev.clone().map(CheckPoint)
126133
}
127134

135+
/// Get the index of this checkpoint (number of checkpoints from the first).
136+
pub fn index(&self) -> u32 {
137+
self.0.index
138+
}
139+
140+
/// Get the skip pointer checkpoint if it exists.
141+
pub fn skip(&self) -> Option<CheckPoint<D>> {
142+
self.0.skip.clone().map(CheckPoint)
143+
}
144+
128145
/// Iterate from this checkpoint in descending height.
129146
pub fn iter(&self) -> CheckPointIter<D> {
130147
self.clone().into_iter()
@@ -134,7 +151,47 @@ impl<D> CheckPoint<D> {
134151
///
135152
/// Returns `None` if checkpoint at `height` does not exist`.
136153
pub fn get(&self, height: u32) -> Option<Self> {
137-
self.range(height..=height).next()
154+
// Quick path for current height
155+
if self.height() == height {
156+
return Some(self.clone());
157+
}
158+
159+
// Use skip pointers for efficient traversal
160+
let mut current = self.clone();
161+
162+
// First, use skip pointers to get close
163+
while current.height() > height {
164+
// Try to use skip pointer if it won't overshoot
165+
if let Some(skip_cp) = current.skip() {
166+
if skip_cp.height() >= height {
167+
current = skip_cp;
168+
continue;
169+
}
170+
}
171+
172+
// Fall back to regular traversal
173+
match current.prev() {
174+
Some(prev) => {
175+
if prev.height() < height {
176+
// Height doesn't exist in the chain
177+
return None;
178+
}
179+
current = prev;
180+
}
181+
None => return None,
182+
}
183+
184+
if current.height() == height {
185+
return Some(current);
186+
}
187+
}
188+
189+
// Check if we found the height after the loop
190+
if current.height() == height {
191+
Some(current)
192+
} else {
193+
None
194+
}
138195
}
139196

140197
/// Iterate checkpoints over a height range.
@@ -147,12 +204,38 @@ impl<D> CheckPoint<D> {
147204
{
148205
let start_bound = range.start_bound().cloned();
149206
let end_bound = range.end_bound().cloned();
150-
self.iter()
151-
.skip_while(move |cp| match end_bound {
152-
core::ops::Bound::Included(inc_bound) => cp.height() > inc_bound,
153-
core::ops::Bound::Excluded(exc_bound) => cp.height() >= exc_bound,
154-
core::ops::Bound::Unbounded => false,
155-
})
207+
208+
// Fast-path to find starting point using skip pointers
209+
let mut current = self.clone();
210+
211+
// Skip past checkpoints that are above the end bound
212+
while match end_bound {
213+
core::ops::Bound::Included(inc_bound) => current.height() > inc_bound,
214+
core::ops::Bound::Excluded(exc_bound) => current.height() >= exc_bound,
215+
core::ops::Bound::Unbounded => false,
216+
} {
217+
// Try to use skip pointer if it won't overshoot
218+
if let Some(skip_cp) = current.skip() {
219+
let use_skip = match end_bound {
220+
core::ops::Bound::Included(inc_bound) => skip_cp.height() > inc_bound,
221+
core::ops::Bound::Excluded(exc_bound) => skip_cp.height() >= exc_bound,
222+
core::ops::Bound::Unbounded => false,
223+
};
224+
if use_skip {
225+
current = skip_cp;
226+
continue;
227+
}
228+
}
229+
230+
// Fall back to regular traversal
231+
match current.prev() {
232+
Some(prev) => current = prev,
233+
None => break,
234+
}
235+
}
236+
237+
// Now iterate normally from the found starting point
238+
current.into_iter()
156239
.take_while(move |cp| match start_bound {
157240
core::ops::Bound::Included(inc_bound) => cp.height() >= inc_bound,
158241
core::ops::Bound::Excluded(exc_bound) => cp.height() > exc_bound,
@@ -167,7 +250,38 @@ impl<D> CheckPoint<D> {
167250
///
168251
/// Returns `None` if no checkpoint exists at or below the given height.
169252
pub fn floor_at(&self, height: u32) -> Option<Self> {
170-
self.range(..=height).next()
253+
// Quick path for current height or higher
254+
if self.height() <= height {
255+
return Some(self.clone());
256+
}
257+
258+
// Use skip pointers for efficient traversal
259+
let mut current = self.clone();
260+
261+
while current.height() > height {
262+
// Try to use skip pointer if it won't undershoot
263+
if let Some(skip_cp) = current.skip() {
264+
if skip_cp.height() > height {
265+
current = skip_cp;
266+
continue;
267+
}
268+
}
269+
270+
// Fall back to regular traversal
271+
match current.prev() {
272+
Some(prev) => {
273+
// If prev is at or below height, we've found our floor
274+
if prev.height() <= height {
275+
return Some(prev);
276+
}
277+
current = prev;
278+
}
279+
None => return None,
280+
}
281+
}
282+
283+
// Current is at or below height
284+
Some(current)
171285
}
172286

173287
/// Returns the checkpoint located a number of heights below this one.
@@ -205,6 +319,8 @@ where
205319
},
206320
data,
207321
prev: None,
322+
skip: None,
323+
index: 0,
208324
}))
209325
}
210326

@@ -269,8 +385,63 @@ where
269385
cp = cp.prev().expect("will break before genesis block");
270386
};
271387

272-
base.extend(core::iter::once((height, data)).chain(tail.into_iter().rev()))
273-
.expect("tail is in order")
388+
// Rebuild the chain with proper indices
389+
let mut result = base.clone();
390+
let base_index = result.index();
391+
392+
// First insert the new block
393+
result = result.push_with_index(height, data, base_index + 1).expect("height is valid");
394+
395+
// Then re-add all the tail blocks with updated indices
396+
let mut current_index = base_index + 2;
397+
for (h, d) in tail.into_iter().rev() {
398+
result = result.push_with_index(h, d, current_index).expect("tail is in order");
399+
current_index += 1;
400+
}
401+
402+
result
403+
}
404+
405+
// Helper method to push with a specific index (internal use)
406+
fn push_with_index(self, height: u32, data: D, new_index: u32) -> Result<Self, Self> {
407+
if self.height() < height {
408+
// Calculate skip pointer
409+
let skip = if new_index >= CHECKPOINT_SKIP_INTERVAL && new_index % CHECKPOINT_SKIP_INTERVAL == 0 {
410+
// Navigate back CHECKPOINT_SKIP_INTERVAL checkpoints
411+
let target_index = new_index - CHECKPOINT_SKIP_INTERVAL;
412+
let mut current = Some(self.0.clone());
413+
loop {
414+
match current {
415+
Some(ref cp) if cp.index == target_index => break,
416+
Some(ref cp) if cp.index < target_index => {
417+
// We've gone too far back, skip pointer not available
418+
current = None;
419+
break;
420+
}
421+
Some(ref cp) => {
422+
current = cp.prev.clone();
423+
}
424+
None => break,
425+
}
426+
}
427+
current
428+
} else {
429+
None
430+
};
431+
432+
Ok(Self(Arc::new(CPInner {
433+
block_id: BlockId {
434+
height,
435+
hash: data.to_blockhash(),
436+
},
437+
data,
438+
prev: Some(self.0),
439+
skip,
440+
index: new_index,
441+
})))
442+
} else {
443+
Err(self)
444+
}
274445
}
275446

276447
/// Puts another checkpoint onto the linked list representing the blockchain.
@@ -279,13 +450,42 @@ where
279450
/// one you are pushing on to.
280451
pub fn push(self, height: u32, data: D) -> Result<Self, Self> {
281452
if self.height() < height {
453+
let new_index = self.0.index + 1;
454+
455+
// Calculate skip pointer
456+
let skip = if new_index >= CHECKPOINT_SKIP_INTERVAL && new_index % CHECKPOINT_SKIP_INTERVAL == 0 {
457+
// Navigate back CHECKPOINT_SKIP_INTERVAL checkpoints
458+
let mut current = Some(self.0.clone());
459+
let mut steps = 0;
460+
loop {
461+
match current {
462+
Some(ref cp) if cp.index == new_index - CHECKPOINT_SKIP_INTERVAL => break,
463+
Some(ref cp) => {
464+
current = cp.prev.clone();
465+
steps += 1;
466+
// Safety check to avoid infinite loop
467+
if steps > CHECKPOINT_SKIP_INTERVAL {
468+
current = None;
469+
break;
470+
}
471+
}
472+
None => break,
473+
}
474+
}
475+
current
476+
} else {
477+
None
478+
};
479+
282480
Ok(Self(Arc::new(CPInner {
283481
block_id: BlockId {
284482
height,
285483
hash: data.to_blockhash(),
286484
},
287485
data,
288486
prev: Some(self.0),
487+
skip,
488+
index: new_index,
289489
})))
290490
} else {
291491
Err(self)

0 commit comments

Comments
 (0)