Skip to content

Commit

Permalink
light-client: Fix verification of blocks between two trusted heights
Browse files Browse the repository at this point in the history
  • Loading branch information
kostko committed Dec 6, 2022
1 parent e9c41de commit ddf1aba
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 7 deletions.
6 changes: 3 additions & 3 deletions light-client/src/components/scheduler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub trait Scheduler: Send + Sync {
///
/// ## Postcondition
/// - The resulting height must be valid according to `valid_schedule`. [LCV-SCHEDULE-POST.1]
#[requires(light_store.highest_trusted_or_verified().is_some())]
#[requires(light_store.highest_trusted_or_verified_before(target_height).is_some())]
#[ensures(valid_schedule(ret, target_height, current_height, light_store))]
fn schedule(
&self,
Expand Down Expand Up @@ -61,7 +61,7 @@ pub fn basic_bisecting_schedule(
target_height: Height,
) -> Height {
let trusted_height = light_store
.highest_trusted_or_verified()
.highest_trusted_or_verified_before(target_height)
.map(|lb| lb.height())
.unwrap();

Expand Down Expand Up @@ -105,7 +105,7 @@ pub fn valid_schedule(
light_store: &dyn LightStore,
) -> bool {
let latest_trusted_height = light_store
.highest_trusted_or_verified()
.highest_trusted_or_verified_before(target_height)
.map(|lb| lb.height())
.unwrap();

Expand Down
11 changes: 7 additions & 4 deletions light-client/src/light_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,8 @@ impl LightClient {
// Get the highest trusted state
let highest = state
.light_store
.highest_trusted_or_verified()
.highest_trusted_or_verified_before(target_height)
.or_else(|| state.light_store.lowest_trusted_or_verified())
.ok_or_else(Error::no_initial_trusted_state)?;

if target_height >= highest.height() {
Expand All @@ -187,7 +188,7 @@ impl LightClient {
// Get the latest trusted state
let trusted_block = state
.light_store
.highest_trusted_or_verified()
.highest_trusted_or_verified_before(target_height)
.ok_or_else(Error::no_initial_trusted_state)?;

if target_height < trusted_block.height() {
Expand Down Expand Up @@ -267,7 +268,8 @@ impl LightClient {
) -> Result<LightBlock, Error> {
let trusted_state = state
.light_store
.highest_trusted_or_verified()
.highest_trusted_or_verified_before(target_height)
.or_else(|| state.light_store.lowest_trusted_or_verified())
.ok_or_else(Error::no_initial_trusted_state)?;

Err(Error::target_lower_than_trusted_state(
Expand Down Expand Up @@ -304,7 +306,8 @@ impl LightClient {

let root = state
.light_store
.highest_trusted_or_verified()
.highest_trusted_or_verified_before(target_height)
.or_else(|| state.light_store.lowest_trusted_or_verified())
.ok_or_else(Error::no_initial_trusted_state)?;

assert!(root.height() >= target_height);
Expand Down
13 changes: 13 additions & 0 deletions light-client/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ pub trait LightStore: Debug + Send + Sync {
/// Get the light block of greatest height with the given status.
fn highest(&self, status: Status) -> Option<LightBlock>;

/// Get the light block of greatest hight before the given height with the given status.
fn highest_before(&self, height: Height, status: Status) -> Option<LightBlock>;

/// Get the light block of lowest height with the given status.
fn lowest(&self, status: Status) -> Option<LightBlock>;

Expand Down Expand Up @@ -76,6 +79,16 @@ pub trait LightStore: Debug + Send + Sync {
})
}

/// Get the first light block before the given height with the trusted or verified status.
fn highest_trusted_or_verified_before(&self, height: Height) -> Option<LightBlock> {
let highest_trusted = self.highest_before(height, Status::Trusted);
let highest_verified = self.highest_before(height, Status::Verified);

std_ext::option::select(highest_trusted, highest_verified, |t, v| {
std_ext::cmp::max_by_key(t, v, |lb| lb.height())
})
}

/// Get the light block of lowest height with the trusted or verified status.
fn lowest_trusted_or_verified(&self) -> Option<LightBlock> {
let lowest_trusted = self.lowest(Status::Trusted);
Expand Down
9 changes: 9 additions & 0 deletions light-client/src/store/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ impl LightStore for MemoryStore {
.map(|(_, e)| e.light_block.clone())
}

fn highest_before(&self, height: Height, status: Status) -> Option<LightBlock> {
self.store
.iter()
.filter(|(_, e)| e.status == status)
.filter(|(h, _)| h <= &&height)
.max_by_key(|(&height, _)| height)
.map(|(_, e)| e.light_block.clone())
}

fn lowest(&self, status: Status) -> Option<LightBlock> {
self.store
.iter()
Expand Down
15 changes: 15 additions & 0 deletions light-client/src/store/sled.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ impl LightStore for SledStore {
self.db(status).iter().next_back()
}

fn highest_before(&self, height: Height, status: Status) -> Option<LightBlock> {
// TODO: Should use .range(..=key_bytes(height)).next_back() instead for more efficiency.
self.db(status).iter().rfind(|lb| lb.height() <= height)
}

fn lowest(&self, status: Status) -> Option<LightBlock> {
self.db(status).iter().next()
}
Expand Down Expand Up @@ -109,6 +114,16 @@ mod tests {
})
}

#[test]
fn highest_before_returns_correct_block() {
with_blocks(10, |mut db, blocks| {
for block in blocks {
db.insert(block.clone(), Status::Verified);
assert_eq!(db.highest_before(block.height(), Status::Verified), Some(block));
}
})
}

#[test]
fn lowest_returns_earliest_block() {
with_blocks(10, |mut db, blocks| {
Expand Down

0 comments on commit ddf1aba

Please sign in to comment.