Skip to content

Commit c6c59e1

Browse files
committed
[local_chain] Fix incorrect optimisation logic in update()
Within `update()`, it is not always necessary to call `fix_links()`. The logic to detect this was wrong previously. Add test that would fail with the previous logic.
1 parent ec1eb56 commit c6c59e1

File tree

2 files changed

+46
-34
lines changed

2 files changed

+46
-34
lines changed

crates/chain/src/local_chain.rs

Lines changed: 23 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,6 @@ struct CPInner {
2323
prev: Option<Arc<CPInner>>,
2424
}
2525

26-
/// Occurs when the caller contructs a [`CheckPoint`] with a height that is not higher than the
27-
/// previous checkpoint it points to.
28-
#[derive(Debug, Clone, PartialEq)]
29-
pub struct NewCheckPointError {
30-
/// The height of the new checkpoint.
31-
pub new_height: u32,
32-
/// The height of the previous checkpoint.
33-
pub prev_height: u32,
34-
}
35-
36-
impl core::fmt::Display for NewCheckPointError {
37-
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
38-
write!(f, "cannot construct checkpoint with a height ({}) that is not higher than the previous checkpoint ({})", self.new_height, self.prev_height)
39-
}
40-
}
41-
42-
impl std::error::Error for NewCheckPointError {}
43-
4426
impl CheckPoint {
4527
/// Construct a [`CheckPoint`] from a [`BlockId`].
4628
pub fn new(block: BlockId) -> Self {
@@ -248,27 +230,30 @@ impl LocalChain {
248230
pub fn update(&mut self, new_tip: CheckPoint) -> Result<ChangeSet, CannotConnectError> {
249231
let mut updated_cps = BTreeMap::<u32, CheckPoint>::new();
250232
let mut agreement_height = Option::<u32>::None;
251-
let mut complete_match = false;
233+
let mut agreement_ptr_matches = false;
252234

253235
for cp in new_tip.iter() {
254236
let block = cp.block_id();
255-
let original_cp = self.checkpoints.get(&block.height);
256237

257-
// if original block of height does not exist, or if the hash does not match we will
258-
// need to update the original checkpoint at that height
259-
if original_cp.map(CheckPoint::block_id) != Some(block) {
260-
updated_cps.insert(block.height, cp.clone());
261-
}
238+
match self.checkpoints.get(&block.height) {
239+
Some(original_cp) if original_cp.block_id() == block => {
240+
let ptr_matches = Arc::as_ptr(&original_cp.0) == Arc::as_ptr(&cp.0);
241+
242+
// only record the first agreement height
243+
if agreement_height.is_none() && original_cp.block_id() == block {
244+
agreement_height = Some(block.height);
245+
agreement_ptr_matches = ptr_matches;
246+
}
262247

263-
if let Some(original_cp) = original_cp {
264-
// record the first agreement height
265-
if agreement_height.is_none() && original_cp.block_id() == block {
266-
agreement_height = Some(block.height);
248+
// break if the internal pointers of the checkpoints are the same
249+
if ptr_matches {
250+
break;
251+
}
267252
}
268-
// break if the internal pointers of the checkpoints are the same
269-
if Arc::as_ptr(&original_cp.0) == Arc::as_ptr(&cp.0) {
270-
complete_match = true;
271-
break;
253+
// only insert into `updated_cps` if cp is actually updated (original cp is `None`,
254+
// or block ids do not match)
255+
_ => {
256+
updated_cps.insert(block.height, cp.clone());
272257
}
273258
}
274259
}
@@ -315,7 +300,11 @@ impl LocalChain {
315300
if let Some(&start_height) = updated_cps.keys().next() {
316301
self.checkpoints.split_off(&invalidate_lb);
317302
self.checkpoints.append(&mut updated_cps);
318-
if !self.is_empty() && !complete_match {
303+
304+
// we never need to fix links if either:
305+
// 1. the original chain is empty
306+
// 2. the pointers match at the first point of agreement (where the block ids are equal)
307+
if !(self.is_empty() || agreement_ptr_matches) {
319308
self.fix_links(start_height);
320309
}
321310
}

crates/chain/tests/test_local_chain.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,29 @@ fn update() {
213213
new_tip: chain_update![(1, h!("B'")), (2, h!("C'")), (3, h!("D"))],
214214
exp: ExpectedResult::Err(CannotConnectError { try_include: BlockId { height: 0, hash: h!("A") } }),
215215
},
216+
// Introduce blocks between two points of agreement
217+
// | 0 | 1 | 2 | 3 | 4 | 5
218+
// chain | A B D E
219+
// update | A C E F
220+
TestLocalChain {
221+
name: "introduce blocks between two points of agreement",
222+
chain: local_chain![(0, h!("A")), (1, h!("B")), (3, h!("D")), (4, h!("E"))],
223+
new_tip: chain_update![(0, h!("A")), (2, h!("C")), (4, h!("E")), (5, h!("F"))],
224+
exp: ExpectedResult::Ok {
225+
changeset: &[
226+
(2, Some(h!("C"))),
227+
(5, Some(h!("F"))),
228+
],
229+
init_changeset: &[
230+
(0, Some(h!("A"))),
231+
(1, Some(h!("B"))),
232+
(2, Some(h!("C"))),
233+
(3, Some(h!("D"))),
234+
(4, Some(h!("E"))),
235+
(5, Some(h!("F"))),
236+
],
237+
},
238+
}
216239
]
217240
.into_iter()
218241
.for_each(TestLocalChain::run);

0 commit comments

Comments
 (0)