use core::fmt;
use contracts::*;
pub use crate::verifier::options::Options;
use crate::{
components::{clock::Clock, io::*, scheduler::*},
contracts::*,
errors::Error,
state::State,
verifier::{
types::{Height, LightBlock, PeerId, Status},
Verdict, Verifier,
},
};
pub struct LightClient {
pub peer: PeerId,
pub options: Options,
clock: Box<dyn Clock>,
scheduler: Box<dyn Scheduler>,
verifier: Box<dyn Verifier>,
io: Box<dyn Io>,
}
impl fmt::Debug for LightClient {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("LightClient")
.field("peer", &self.peer)
.field("options", &self.options)
.finish()
}
}
impl LightClient {
pub fn new(
peer: PeerId,
options: Options,
clock: impl Clock + 'static,
scheduler: impl Scheduler + 'static,
verifier: impl Verifier + 'static,
io: impl Io + 'static,
) -> Self {
Self {
peer,
options,
clock: Box::new(clock),
scheduler: Box::new(scheduler),
verifier: Box::new(verifier),
io: Box::new(io),
}
}
pub fn from_boxed(
peer: PeerId,
options: Options,
clock: Box<dyn Clock>,
scheduler: Box<dyn Scheduler>,
verifier: Box<dyn Verifier>,
io: Box<dyn Io>,
) -> Self {
Self {
peer,
options,
clock,
scheduler,
verifier,
io,
}
}
pub fn verify_to_highest(&mut self, state: &mut State) -> Result<LightBlock, Error> {
let target_block = self
.io
.fetch_light_block(AtHeight::Highest)
.map_err(Error::io)?;
self.verify_to_target(target_block.height(), state)
}
#[allow(clippy::nonminimal_bool)]
#[ensures(
ret.is_ok() -> trusted_store_contains_block_at_target_height(
state.light_store.as_ref(),
target_height,
)
)]
pub fn verify_to_target(
&self,
target_height: Height,
state: &mut State,
) -> Result<LightBlock, Error> {
if let Some(light_block) = state.light_store.get_trusted_or_verified(target_height) {
return Ok(light_block);
}
let highest = state
.light_store
.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() {
self.verify_forward(target_height, state)
} else {
self.verify_backward(target_height, state)
}
}
fn verify_forward(
&self,
target_height: Height,
state: &mut State,
) -> Result<LightBlock, Error> {
let mut current_height = target_height;
loop {
let now = self.clock.now();
let trusted_block = state
.light_store
.highest_trusted_or_verified_before(target_height)
.ok_or_else(Error::no_initial_trusted_state)?;
if target_height < trusted_block.height() {
return Err(Error::target_lower_than_trusted_state(
target_height,
trusted_block.height(),
));
}
if !is_within_trust_period(&trusted_block, self.options.trusting_period, now) {
return Err(Error::trusted_state_outside_trusting_period(
Box::new(trusted_block),
self.options,
));
}
state.trace_block(target_height, current_height);
if target_height == trusted_block.height() {
return Ok(trusted_block);
}
let (current_block, status) = self.get_or_fetch_block(current_height, state)?;
let verdict = self.verifier.verify_update_header(
current_block.as_untrusted_state(),
trusted_block.as_trusted_state(),
&self.options,
now,
);
match verdict {
Verdict::Success => {
let new_status = Status::most_trusted(Status::Verified, status);
state.light_store.update(¤t_block, new_status);
state.trace_block(current_height, trusted_block.height());
},
Verdict::Invalid(e) => {
state.light_store.update(¤t_block, Status::Failed);
return Err(Error::invalid_light_block(e));
},
Verdict::NotEnoughTrust(_) => {
state.light_store.update(¤t_block, Status::Unverified);
},
}
current_height =
self.scheduler
.schedule(state.light_store.as_ref(), current_height, target_height);
}
}
#[doc(hidden)]
#[cfg(not(feature = "unstable"))]
fn verify_backward(
&self,
target_height: Height,
state: &mut State,
) -> Result<LightBlock, Error> {
let trusted_state = state
.light_store
.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(
target_height,
trusted_state.height(),
))
}
#[cfg(feature = "unstable")]
fn verify_backward(
&self,
target_height: Height,
state: &mut State,
) -> Result<LightBlock, Error> {
use tendermint::crypto::default::Sha256;
let root = state
.light_store
.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);
if !is_within_trust_period(&root, self.options.trusting_period, self.clock.now()) {
return Err(Error::trusted_state_outside_trusting_period(
Box::new(root),
self.options,
));
}
let range = (target_height.value()..root.height().value()).rev();
let heights = range.map(|h| Height::try_from(h).unwrap());
let mut latest = root;
for height in heights {
let (current, _status) = self.get_or_fetch_block(height, state)?;
let latest_last_block_id = latest
.signed_header
.header
.last_block_id
.ok_or_else(|| Error::missing_last_block_id(latest.height()))?;
let current_hash = current.signed_header.header.hash_with::<Sha256>();
if current_hash != latest_last_block_id.hash {
return Err(Error::invalid_adjacent_headers(
current_hash,
latest_last_block_id.hash,
));
}
state.light_store.insert(current.clone(), Status::Trusted);
state.light_store.insert(latest.clone(), Status::Trusted);
state.trace_block(latest.height(), current.height());
latest = current;
}
assert_eq!(latest.height(), target_height);
Ok(latest)
}
#[ensures(ret.as_ref().map(|(lb, _)| lb.provider == self.peer).unwrap_or(true))]
pub fn get_or_fetch_block(
&self,
height: Height,
state: &mut State,
) -> Result<(LightBlock, Status), Error> {
let block = state.light_store.get_non_failed(height);
if let Some(block) = block {
return Ok(block);
}
let block = self
.io
.fetch_light_block(AtHeight::At(height))
.map_err(Error::io)?;
state.light_store.insert(block.clone(), Status::Unverified);
Ok((block, Status::Unverified))
}
pub fn get_target_block_or_latest(
&mut self,
height: Height,
state: &mut State,
) -> Result<TargetOrLatest, Error> {
let block = state.light_store.get_non_failed(height);
if let Some((block, _)) = block {
return Ok(TargetOrLatest::Target(block));
}
let block = self.io.fetch_light_block(AtHeight::At(height));
if let Ok(block) = block {
return Ok(TargetOrLatest::Target(block));
}
let latest = self
.io
.fetch_light_block(AtHeight::Highest)
.map_err(Error::io)?;
if latest.height() == height {
Ok(TargetOrLatest::Target(latest))
} else {
Ok(TargetOrLatest::Latest(latest))
}
}
}
pub enum TargetOrLatest {
Latest(LightBlock),
Target(LightBlock),
}