From ddf1aba6a2d7bdf98551e92f03a318f9ad794408 Mon Sep 17 00:00:00 2001 From: Jernej Kos Date: Tue, 6 Dec 2022 18:01:05 +0100 Subject: [PATCH] light-client: Fix verification of blocks between two trusted heights --- light-client/src/components/scheduler.rs | 6 +++--- light-client/src/light_client.rs | 11 +++++++---- light-client/src/store.rs | 13 +++++++++++++ light-client/src/store/memory.rs | 9 +++++++++ light-client/src/store/sled.rs | 15 +++++++++++++++ 5 files changed, 47 insertions(+), 7 deletions(-) diff --git a/light-client/src/components/scheduler.rs b/light-client/src/components/scheduler.rs index 568784377..1d613a2ea 100644 --- a/light-client/src/components/scheduler.rs +++ b/light-client/src/components/scheduler.rs @@ -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, @@ -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(); @@ -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(); diff --git a/light-client/src/light_client.rs b/light-client/src/light_client.rs index 3ebb58498..71dc8ccde 100644 --- a/light-client/src/light_client.rs +++ b/light-client/src/light_client.rs @@ -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() { @@ -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() { @@ -267,7 +268,8 @@ impl LightClient { ) -> Result { 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( @@ -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); diff --git a/light-client/src/store.rs b/light-client/src/store.rs index f9f395cf3..b3ae062d6 100644 --- a/light-client/src/store.rs +++ b/light-client/src/store.rs @@ -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; + /// Get the light block of greatest hight before the given height with the given status. + fn highest_before(&self, height: Height, status: Status) -> Option; + /// Get the light block of lowest height with the given status. fn lowest(&self, status: Status) -> Option; @@ -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 { + 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 { let lowest_trusted = self.lowest(Status::Trusted); diff --git a/light-client/src/store/memory.rs b/light-client/src/store/memory.rs index 463948b4c..2b7aa520c 100644 --- a/light-client/src/store/memory.rs +++ b/light-client/src/store/memory.rs @@ -72,6 +72,15 @@ impl LightStore for MemoryStore { .map(|(_, e)| e.light_block.clone()) } + fn highest_before(&self, height: Height, status: Status) -> Option { + 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 { self.store .iter() diff --git a/light-client/src/store/sled.rs b/light-client/src/store/sled.rs index a749fa7de..68c73a8ff 100644 --- a/light-client/src/store/sled.rs +++ b/light-client/src/store/sled.rs @@ -79,6 +79,11 @@ impl LightStore for SledStore { self.db(status).iter().next_back() } + fn highest_before(&self, height: Height, status: Status) -> Option { + // 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 { self.db(status).iter().next() } @@ -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| {