Skip to content

Commit b73dcd6

Browse files
committed
feat(chain)!: make CheckPoint data field optional
1 parent faf520d commit b73dcd6

File tree

2 files changed

+115
-28
lines changed

2 files changed

+115
-28
lines changed

crates/chain/src/local_chain.rs

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ where
2727

2828
for cp in init_cp.iter() {
2929
if cp.height() >= start_height {
30-
extension.insert(cp.height(), cp.data());
30+
if let Some(data) = cp.data() {
31+
extension.insert(cp.height(), data);
32+
}
3133
} else {
3234
base = Some(cp);
3335
break;
@@ -45,12 +47,19 @@ where
4547
};
4648
}
4749

48-
let new_tip = match base {
50+
let mut new_tip = match base {
4951
Some(base) => base
5052
.extend(extension)
5153
.expect("extension is strictly greater than base"),
5254
None => LocalChain::from_blocks(extension)?.tip(),
5355
};
56+
57+
if new_tip.data_ref().is_none() {
58+
new_tip = new_tip
59+
.find_data(new_tip.height())
60+
.expect("genesis checkpoint should have data");
61+
}
62+
5463
init_cp = new_tip;
5564
}
5665

@@ -322,11 +331,7 @@ where
322331
/// recover the current chain.
323332
pub fn initial_changeset(&self) -> ChangeSet<D> {
324333
ChangeSet {
325-
blocks: self
326-
.tip
327-
.iter()
328-
.map(|cp| (cp.height(), Some(cp.data())))
329-
.collect(),
334+
blocks: self.tip.iter().map(|cp| (cp.height(), cp.data())).collect(),
330335
}
331336
}
332337

@@ -349,6 +354,20 @@ where
349354
update_hash: Some(data.to_blockhash()),
350355
});
351356
}
357+
358+
// If this `CheckPoint` is an empty placeholder, append the `data` to it.
359+
if original_cp.data_ref().is_none() {
360+
let mut changeset = ChangeSet::<D>::default();
361+
changeset.blocks.insert(height, Some(data));
362+
self.apply_changeset(&changeset)
363+
.map_err(|_| AlterCheckPointError {
364+
height: 0,
365+
original_hash: self.genesis_hash(),
366+
update_hash: None,
367+
})?;
368+
return Ok(changeset);
369+
}
370+
352371
return Ok(ChangeSet::default());
353372
}
354373

@@ -634,7 +653,9 @@ where
634653
match (curr_orig.as_ref(), curr_update.as_ref()) {
635654
// Update block that doesn't exist in the original chain
636655
(o, Some(u)) if Some(u.height()) > o.map(|o| o.height()) => {
637-
changeset.blocks.insert(u.height(), Some(u.data()));
656+
if let Some(data) = u.data() {
657+
changeset.blocks.insert(u.height(), Some(data));
658+
}
638659
prev_update = curr_update.take();
639660
}
640661
// Original block that isn't in the update
@@ -685,7 +706,7 @@ where
685706
} else {
686707
// We have an invalidation height so we set the height to the updated hash and
687708
// also purge all the original chain block hashes above this block.
688-
changeset.blocks.insert(u.height(), Some(u.data()));
709+
changeset.blocks.insert(u.height(), u.data());
689710
for invalidated_height in potentially_invalidated_heights.drain(..) {
690711
changeset.blocks.insert(invalidated_height, None);
691712
}

crates/core/src/checkpoint.rs

Lines changed: 85 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ impl<D> Clone for CheckPoint<D> {
2424
struct CPInner<D> {
2525
/// Block id
2626
block_id: BlockId,
27-
/// Data.
28-
data: D,
27+
/// Data (if any).
28+
data: Option<D>,
2929
/// Previous checkpoint (if any).
3030
prev: Option<Arc<CPInner<D>>>,
3131
}
@@ -68,6 +68,11 @@ impl<D> Drop for CPInner<D> {
6868
pub trait ToBlockHash {
6969
/// Returns the [`BlockHash`] for the associated [`CheckPoint`] `data` type.
7070
fn to_blockhash(&self) -> BlockHash;
71+
72+
/// Returns `None` if the type has no knowledge of the previous [`BlockHash`].
73+
fn prev_blockhash(&self) -> Option<BlockHash> {
74+
None
75+
}
7176
}
7277

7378
impl ToBlockHash for BlockHash {
@@ -80,6 +85,10 @@ impl ToBlockHash for Header {
8085
fn to_blockhash(&self) -> BlockHash {
8186
self.block_hash()
8287
}
88+
89+
fn prev_blockhash(&self) -> Option<BlockHash> {
90+
Some(self.prev_blockhash)
91+
}
8392
}
8493

8594
impl<D> PartialEq for CheckPoint<D> {
@@ -92,13 +101,13 @@ impl<D> PartialEq for CheckPoint<D> {
92101

93102
// Methods for any `D`
94103
impl<D> CheckPoint<D> {
95-
/// Get a reference of the `data` of the checkpoint.
96-
pub fn data_ref(&self) -> &D {
97-
&self.0.data
104+
/// Get a reference of the `data` of the checkpoint if it exists.
105+
pub fn data_ref(&self) -> Option<&D> {
106+
self.0.data.as_ref()
98107
}
99108

100-
/// Get the `data` of a the checkpoint.
101-
pub fn data(&self) -> D
109+
/// Get the `data` of the checkpoint if it exists.
110+
pub fn data(&self) -> Option<D>
102111
where
103112
D: Clone,
104113
{
@@ -170,6 +179,17 @@ impl<D> CheckPoint<D> {
170179
self.range(..=height).next()
171180
}
172181

182+
/// Finds the checkpoint with `data` at `height` if one exists, otherwise the neareast
183+
/// checkpoint with `data` at a lower height.
184+
///
185+
/// This is equivalent to taking the “floor” of "height" over this checkpoint chain, filtering
186+
/// out any placeholder entries that do not contain any `data`.
187+
///
188+
/// Returns `None` if no checkpoint with `data` exists at or below the given height.
189+
pub fn find_data(&self, height: u32) -> Option<Self> {
190+
self.range(..=height).find(|cp| cp.data_ref().is_some())
191+
}
192+
173193
/// Returns the checkpoint located a number of heights below this one.
174194
///
175195
/// This is a convenience wrapper for [`CheckPoint::floor_at`], subtracting `to_subtract` from
@@ -198,13 +218,30 @@ where
198218
/// Construct a new base [`CheckPoint`] from given `height` and `data` at the front of a linked
199219
/// list.
200220
pub fn new(height: u32, data: D) -> Self {
221+
// If `data` has a `prev_blockhash`, create a placeholder checkpoint one height below.
222+
let prev = if height > 0 {
223+
match data.prev_blockhash() {
224+
Some(prev_blockhash) => Some(Arc::new(CPInner {
225+
block_id: BlockId {
226+
height: height - 1,
227+
hash: prev_blockhash,
228+
},
229+
data: None,
230+
prev: None,
231+
})),
232+
None => None,
233+
}
234+
} else {
235+
None
236+
};
237+
201238
Self(Arc::new(CPInner {
202239
block_id: BlockId {
203240
height,
204241
hash: data.to_blockhash(),
205242
},
206-
data,
207-
prev: None,
243+
data: Some(data),
244+
prev,
208245
}))
209246
}
210247

@@ -251,21 +288,30 @@ where
251288
let mut tail = vec![];
252289
let base = loop {
253290
if cp.height() == height {
254-
if cp.hash() == data.to_blockhash() {
255-
return self;
291+
let same_hash = cp.hash() == data.to_blockhash();
292+
if same_hash {
293+
if cp.data().is_some() {
294+
return self;
295+
} else {
296+
// If `CheckPoint` is a placeholder, return previous `CheckPoint`.
297+
break cp.prev().expect("can't be called on genesis block");
298+
}
299+
} else {
300+
assert_ne!(cp.height(), 0, "cannot replace genesis block");
301+
// If we have a conflict we just return the inserted data because the tail is by
302+
// implication invalid.
303+
tail = vec![];
304+
break cp.prev().expect("can't be called on genesis block");
256305
}
257-
assert_ne!(cp.height(), 0, "cannot replace genesis block");
258-
// If we have a conflict we just return the inserted data because the tail is by
259-
// implication invalid.
260-
tail = vec![];
261-
break cp.prev().expect("can't be called on genesis block");
262306
}
263307

264308
if cp.height() < height {
265309
break cp;
266310
}
267311

268-
tail.push((cp.height(), cp.data()));
312+
if let Some(d) = cp.data() {
313+
tail.push((cp.height(), d));
314+
}
269315
cp = cp.prev().expect("will break before genesis block");
270316
};
271317

@@ -277,15 +323,35 @@ where
277323
///
278324
/// Returns an `Err(self)` if the block you are pushing on is not at a greater height that the
279325
/// one you are pushing on to.
326+
///
327+
/// If `height` is non-contiguous and `data.prev_blockhash()` is available, a placeholder is
328+
/// created at height - 1.
280329
pub fn push(self, height: u32, data: D) -> Result<Self, Self> {
281330
if self.height() < height {
331+
let mut current_cp = self.0.clone();
332+
333+
// If non-contiguous and `prev_blockhash` exists, insert a placeholder at height - 1.
334+
if height > self.height() + 1 {
335+
if let Some(prev_hash) = data.prev_blockhash() {
336+
let empty = Arc::new(CPInner {
337+
block_id: BlockId {
338+
height: height - 1,
339+
hash: prev_hash,
340+
},
341+
data: None,
342+
prev: Some(current_cp),
343+
});
344+
current_cp = empty;
345+
}
346+
}
347+
282348
Ok(Self(Arc::new(CPInner {
283349
block_id: BlockId {
284350
height,
285351
hash: data.to_blockhash(),
286352
},
287-
data,
288-
prev: Some(self.0),
353+
data: Some(data),
354+
prev: Some(current_cp),
289355
})))
290356
} else {
291357
Err(self)

0 commit comments

Comments
 (0)