Skip to content

Commit

Permalink
Merge pull request #576 from Zeegomo/lca
Browse files Browse the repository at this point in the history
Add lowest common ancestor function to storage
  • Loading branch information
zeegomo committed Jun 16, 2021
2 parents c6e2faa + 84a35e1 commit 8b05129
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 18 deletions.
72 changes: 68 additions & 4 deletions chain-storage/src/block_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,10 +348,11 @@ impl BlockStore {
return Ok(true);
}

self.info_tree
.get(block_id)
.map(|maybe_block| maybe_block.is_some())
.map_err(Into::into)
self.block_exists_volatile(block_id)
}

fn block_exists_volatile(&self, block_id: &[u8]) -> Result<bool, Error> {
self.info_tree.contains_key(block_id).map_err(Into::into)
}

/// Determine whether block identified by `ancestor_id` is an ancestor of
Expand Down Expand Up @@ -467,6 +468,69 @@ impl BlockStore {
Ok(current)
}

/// Find the lowest common ancestor block (commonly referred to as lca) of two blocks in the chain
///
/// In case there are more than one common ancestor, pick the one with the biggest chain length.
pub fn find_lowest_common_ancestor(
&self,
block1: &[u8],
block2: &[u8],
) -> Result<Option<BlockInfo>, Error> {
let pstore1 = self.permanent.get_block_info(block1)?;
let pstore2 = self.permanent.get_block_info(block2)?;

match (pstore1, pstore2) {
// if both blocks are in permanent store then the oldest one is the lca
(Some(block1), Some(block2)) => {
if block1.chain_length() < block2.chain_length() {
return Ok(Some(block1));
} else {
return Ok(Some(block2));
}
}
// similarly, if only one of then is in the permament storage then it's the lca
(Some(block), None) => {
if self.block_exists_volatile(block2)? {
return Ok(Some(block));
}
return Err(Error::BlockNotFound);
}
(None, Some(block)) => {
if self.block_exists_volatile(block1)? {
return Ok(Some(block));
}
return Err(Error::BlockNotFound);
}
_ => (),
}

// if we are at this stage it means that no block was found in the permanent storage, thus
// we only need to search among volatile blocks
let mut current1 = self.get_block_info_volatile(block1)?;
let mut current2 = self.get_block_info_volatile(block2)?;

// let current1 be the block deeper in the chain
if current1.chain_length() > current2.chain_length() {
std::mem::swap(&mut current1, &mut current2);
}

current2 = self.get_nth_ancestor(
current2.id().as_ref(),
current2.chain_length() - current1.chain_length(),
)?;

while current2.id() != current1.id() && current1.chain_length() > 0 {
current1 = self.get_block_info(current1.parent_id().as_ref())?;
current2 = self.get_block_info(current2.parent_id().as_ref())?;
}

if current1.id() != current2.id() {
return Ok(None);
}

Ok(Some(current1))
}

/// Move all blocks up to the provided block ID to the permanent block
/// storage.
///
Expand Down
128 changes: 114 additions & 14 deletions chain-storage/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ use std::{collections::HashSet, iter::FromIterator};

const SIMULTANEOUS_READ_WRITE_ITERS: usize = 50;
const BLOCK_NUM_PERMANENT_TEST: usize = 1024;
const MAIN_BRANCH_LEN: usize = 100;
const SECOND_BRANCH_LEN: usize = 25;
const BIFURCATION_POINT: usize = 50;
const FLUSH_TO_BLOCK: usize = 512;
const FLUSH_TO_BLOCK_2: usize = 768;

Expand Down Expand Up @@ -401,11 +404,11 @@ fn get_blocks_by_chain_length() {
assert_eq!(expected, actual);
}

fn generate_two_branches() -> (tempfile::TempDir, BlockStore, Vec<Block>, Vec<Block>) {
const MAIN_BRANCH_LEN: usize = 100;
const SECOND_BRANCH_LEN: usize = 25;
const BIFURCATION_POINT: usize = 50;

fn generate_two_branches(
main_branch_len: usize,
second_branch_len: usize,
bifurcation_point: usize,
) -> (tempfile::TempDir, BlockStore, Vec<Block>, Vec<Block>) {
let (file, store) = prepare_store();

let mut main_branch_blocks = vec![];
Expand All @@ -424,7 +427,7 @@ fn generate_two_branches() -> (tempfile::TempDir, BlockStore, Vec<Block>, Vec<Bl

main_branch_blocks.push(genesis_block);

for _i in 1..MAIN_BRANCH_LEN {
for _i in 1..main_branch_len {
let block_info = BlockInfo::new(
block.id.serialize_as_vec(),
block.parent.serialize_as_vec(),
Expand All @@ -437,11 +440,11 @@ fn generate_two_branches() -> (tempfile::TempDir, BlockStore, Vec<Block>, Vec<Bl
block = block.make_child(None);
}

let mut second_branch_blocks = vec![main_branch_blocks[BIFURCATION_POINT].clone()];
let mut second_branch_blocks = vec![main_branch_blocks[bifurcation_point].clone()];

block = main_branch_blocks[BIFURCATION_POINT].make_child(None);
block = main_branch_blocks[bifurcation_point].make_child(None);

for _i in 1..SECOND_BRANCH_LEN {
for _i in 1..second_branch_len {
let block_info = BlockInfo::new(
block.id.serialize_as_vec(),
block.parent.serialize_as_vec(),
Expand All @@ -457,12 +460,105 @@ fn generate_two_branches() -> (tempfile::TempDir, BlockStore, Vec<Block>, Vec<Bl
(file, store, main_branch_blocks, second_branch_blocks)
}

#[test]
fn lca_same_block() {
let (_, store, main_branch, _) =
generate_two_branches(MAIN_BRANCH_LEN, SECOND_BRANCH_LEN, BIFURCATION_POINT);
assert_eq!(
store
.find_lowest_common_ancestor(
&main_branch[0].id.serialize_as_vec()[..],
&main_branch[0].id.serialize_as_vec()[..]
)
.unwrap()
.unwrap()
.id()
.as_ref(),
&main_branch[0].id.serialize_as_vec()[..]
)
}

#[test]
fn lca_ancestor() {
let (_, store, main_branch, second_branch) =
generate_two_branches(MAIN_BRANCH_LEN, SECOND_BRANCH_LEN, BIFURCATION_POINT);
assert_eq!(
store
.find_lowest_common_ancestor(
&main_branch[1].id.serialize_as_vec()[..],
&second_branch[2].id.serialize_as_vec()[..]
)
.unwrap()
.unwrap()
.id()
.as_ref(),
&main_branch[1].id.serialize_as_vec()[..]
)
}
#[test]
fn lca_different_branches() {
let (_, store, main_branch, second_branch) =
generate_two_branches(MAIN_BRANCH_LEN, SECOND_BRANCH_LEN, BIFURCATION_POINT);
assert_eq!(
store
.find_lowest_common_ancestor(
&main_branch.last().unwrap().id.serialize_as_vec()[..],
&second_branch.last().unwrap().id.serialize_as_vec()[..]
)
.unwrap()
.unwrap()
.id()
.as_ref(),
&main_branch[BIFURCATION_POINT].id.serialize_as_vec()[..]
)
}

#[test]
fn lca_genesis() {
let (_, store, main_branch, second_branch) =
generate_two_branches(MAIN_BRANCH_LEN, SECOND_BRANCH_LEN, 0);

// two branchs diverging at the genesis blocks have the genesis as the lca
assert_eq!(
store
.find_lowest_common_ancestor(
&main_branch[MAIN_BRANCH_LEN - 1].id.serialize_as_vec()[..],
&second_branch[SECOND_BRANCH_LEN - 1].id.serialize_as_vec()[..]
)
.unwrap()
.unwrap()
.id()
.as_ref(),
&main_branch[0].id.serialize_as_vec()[..]
);

let genesis_block = Block::genesis(None);
let genesis_block_info = BlockInfo::new(
genesis_block.id.serialize_as_vec(),
genesis_block.parent.serialize_as_vec(),
genesis_block.chain_length,
);
store
.put_block(&genesis_block.serialize_as_vec(), genesis_block_info)
.unwrap();
// It is in fact possible to have multiple blocks originating from the storage root_id
// In that case there is no common ancestor
assert!(store
.find_lowest_common_ancestor(
&main_branch[MAIN_BRANCH_LEN - 1].id.serialize_as_vec()[..],
&genesis_block.id.serialize_as_vec()[..]
)
.unwrap()
.is_none())
}

#[test]
fn is_ancestor_same_branch() {
const FIRST: usize = 20;
const SECOND: usize = 30;

let (_file, store, main_branch_blocks, _) = generate_two_branches();
let (_file, store, main_branch_blocks, _) =
generate_two_branches(MAIN_BRANCH_LEN, SECOND_BRANCH_LEN, BIFURCATION_POINT);

let result = store
.is_ancestor(
Expand All @@ -479,7 +575,8 @@ fn is_ancestor_wrong_order() {
const FIRST: usize = 30;
const SECOND: usize = 20;

let (_file, store, main_branch_blocks, _) = generate_two_branches();
let (_file, store, main_branch_blocks, _) =
generate_two_branches(MAIN_BRANCH_LEN, SECOND_BRANCH_LEN, BIFURCATION_POINT);

let result = store
.is_ancestor(
Expand All @@ -495,7 +592,8 @@ fn is_ancestor_different_branches() {
const FIRST: usize = 60;
const SECOND: usize = 10;

let (_file, store, main_branch_blocks, second_branch_blocks) = generate_two_branches();
let (_file, store, main_branch_blocks, second_branch_blocks) =
generate_two_branches(MAIN_BRANCH_LEN, SECOND_BRANCH_LEN, BIFURCATION_POINT);

let result = store
.is_ancestor(
Expand All @@ -512,7 +610,8 @@ fn is_ancestor_permanent_volatile() {
const FIRST: usize = 10;
const SECOND: usize = 50;

let (_file, store, main_branch_blocks, _) = generate_two_branches();
let (_file, store, main_branch_blocks, _) =
generate_two_branches(MAIN_BRANCH_LEN, SECOND_BRANCH_LEN, BIFURCATION_POINT);

store
.flush_to_permanent_store(
Expand All @@ -539,7 +638,8 @@ fn is_ancestor_only_permanent() {
const FIRST: usize = 10;
const SECOND: usize = 20;

let (_file, store, main_branch_blocks, _) = generate_two_branches();
let (_file, store, main_branch_blocks, _) =
generate_two_branches(MAIN_BRANCH_LEN, SECOND_BRANCH_LEN, BIFURCATION_POINT);

store
.flush_to_permanent_store(
Expand Down

0 comments on commit 8b05129

Please sign in to comment.