Skip to content

Commit 9d9ac43

Browse files
committed
feat(chain)!: make CheckPoint data field optional
1 parent 126ebda commit 9d9ac43

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
}
@@ -64,6 +64,11 @@ impl<D> Drop for CPInner<D> {
6464
pub trait ToBlockHash {
6565
/// Returns the [`BlockHash`] for the associated [`CheckPoint`] `data` type.
6666
fn to_blockhash(&self) -> BlockHash;
67+
68+
/// Returns `None` if the type has no knowledge of the previous [`BlockHash`].
69+
fn prev_blockhash(&self) -> Option<BlockHash> {
70+
None
71+
}
6772
}
6873

6974
impl ToBlockHash for BlockHash {
@@ -76,6 +81,10 @@ impl ToBlockHash for Header {
7681
fn to_blockhash(&self) -> BlockHash {
7782
self.block_hash()
7883
}
84+
85+
fn prev_blockhash(&self) -> Option<BlockHash> {
86+
Some(self.prev_blockhash)
87+
}
7988
}
8089

8190
impl<D> PartialEq for CheckPoint<D> {
@@ -88,13 +97,13 @@ impl<D> PartialEq for CheckPoint<D> {
8897

8998
// Methods for any `D`
9099
impl<D> CheckPoint<D> {
91-
/// Get a reference of the `data` of the checkpoint.
92-
pub fn data_ref(&self) -> &D {
93-
&self.0.data
100+
/// Get a reference of the `data` of the checkpoint if it exists.
101+
pub fn data_ref(&self) -> Option<&D> {
102+
self.0.data.as_ref()
94103
}
95104

96-
/// Get the `data` of a the checkpoint.
97-
pub fn data(&self) -> D
105+
/// Get the `data` of the checkpoint if it exists.
106+
pub fn data(&self) -> Option<D>
98107
where
99108
D: Clone,
100109
{
@@ -166,6 +175,17 @@ impl<D> CheckPoint<D> {
166175
self.range(..=height).next()
167176
}
168177

178+
/// Finds the checkpoint with `data` at `height` if one exists, otherwise the neareast
179+
/// checkpoint with `data` at a lower height.
180+
///
181+
/// This is equivalent to taking the “floor” of "height" over this checkpoint chain, filtering
182+
/// out any placeholder entries that do not contain any `data`.
183+
///
184+
/// Returns `None` if no checkpoint with `data` exists at or below the given height.
185+
pub fn find_data(&self, height: u32) -> Option<Self> {
186+
self.range(..=height).find(|cp| cp.data_ref().is_some())
187+
}
188+
169189
/// Returns the checkpoint located a number of heights below this one.
170190
///
171191
/// This is a convenience wrapper for [`CheckPoint::floor_at`], subtracting `to_subtract` from
@@ -194,13 +214,30 @@ where
194214
/// Construct a new base [`CheckPoint`] from given `height` and `data` at the front of a linked
195215
/// list.
196216
pub fn new(height: u32, data: D) -> Self {
217+
// If `data` has a `prev_blockhash`, create a placeholder checkpoint one height below.
218+
let prev = if height > 0 {
219+
match data.prev_blockhash() {
220+
Some(prev_blockhash) => Some(Arc::new(CPInner {
221+
block_id: BlockId {
222+
height: height - 1,
223+
hash: prev_blockhash,
224+
},
225+
data: None,
226+
prev: None,
227+
})),
228+
None => None,
229+
}
230+
} else {
231+
None
232+
};
233+
197234
Self(Arc::new(CPInner {
198235
block_id: BlockId {
199236
height,
200237
hash: data.to_blockhash(),
201238
},
202-
data,
203-
prev: None,
239+
data: Some(data),
240+
prev,
204241
}))
205242
}
206243

@@ -247,21 +284,30 @@ where
247284
let mut tail = vec![];
248285
let base = loop {
249286
if cp.height() == height {
250-
if cp.hash() == data.to_blockhash() {
251-
return self;
287+
let same_hash = cp.hash() == data.to_blockhash();
288+
if same_hash {
289+
if cp.data().is_some() {
290+
return self;
291+
} else {
292+
// If `CheckPoint` is a placeholder, return previous `CheckPoint`.
293+
break cp.prev().expect("can't be called on genesis block");
294+
}
295+
} else {
296+
assert_ne!(cp.height(), 0, "cannot replace genesis block");
297+
// If we have a conflict we just return the inserted data because the tail is by
298+
// implication invalid.
299+
tail = vec![];
300+
break cp.prev().expect("can't be called on genesis block");
252301
}
253-
assert_ne!(cp.height(), 0, "cannot replace genesis block");
254-
// If we have a conflict we just return the inserted data because the tail is by
255-
// implication invalid.
256-
tail = vec![];
257-
break cp.prev().expect("can't be called on genesis block");
258302
}
259303

260304
if cp.height() < height {
261305
break cp;
262306
}
263307

264-
tail.push((cp.height(), cp.data()));
308+
if let Some(d) = cp.data() {
309+
tail.push((cp.height(), d));
310+
}
265311
cp = cp.prev().expect("will break before genesis block");
266312
};
267313

@@ -273,15 +319,35 @@ where
273319
///
274320
/// Returns an `Err(self)` if the block you are pushing on is not at a greater height that the
275321
/// one you are pushing on to.
322+
///
323+
/// If `height` is non-contiguous and `data.prev_blockhash()` is available, a placeholder is
324+
/// created at height - 1.
276325
pub fn push(self, height: u32, data: D) -> Result<Self, Self> {
277326
if self.height() < height {
327+
let mut current_cp = self.0.clone();
328+
329+
// If non-contiguous and `prev_blockhash` exists, insert a placeholder at height - 1.
330+
if height > self.height() + 1 {
331+
if let Some(prev_hash) = data.prev_blockhash() {
332+
let empty = Arc::new(CPInner {
333+
block_id: BlockId {
334+
height: height - 1,
335+
hash: prev_hash,
336+
},
337+
data: None,
338+
prev: Some(current_cp),
339+
});
340+
current_cp = empty;
341+
}
342+
}
343+
278344
Ok(Self(Arc::new(CPInner {
279345
block_id: BlockId {
280346
height,
281347
hash: data.to_blockhash(),
282348
},
283-
data,
284-
prev: Some(self.0),
349+
data: Some(data),
350+
prev: Some(current_cp),
285351
})))
286352
} else {
287353
Err(self)

0 commit comments

Comments
 (0)