Extract tm-stateless-verifier crate

This commit is contained in:
hu55a1n1 2023-12-31 13:12:32 -08:00
parent 931c7b0d6f
commit 57d71ed391
6 changed files with 179 additions and 0 deletions

View file

@ -0,0 +1,2 @@
Cargo.lock

View file

@ -0,0 +1,9 @@
[package]
name = "tm-stateless-verifier"
version = "0.1.0"
edition = "2021"
[dependencies]
displaydoc = { version = "0.2.4", default-features = false }
tendermint = { version = "=0.34.0", default-features = false }
tendermint-light-client = { version = "=0.34.0", default-features = false, features = ["rust-crypto"] }

View file

@ -0,0 +1,34 @@
use alloc::boxed::Box;
use displaydoc::Display;
use tendermint::{block::Height, Hash};
use tendermint_light_client::{
builder::error::Error as TmBuilderError, errors::Error as LightClientError,
};
#[derive(Debug, Display)]
pub enum Error {
/// empty trace
EmptyTrace,
/// first block in trace does not match trusted (expected {expected:?}, found {found:?})
FirstTraceBlockNotTrusted {
expected: (Height, Hash),
found: (Height, Hash),
},
/// verification failure (`{0}`)
VerificationFailure(Box<LightClientError>),
/// failed to build light client (`{0}`)
LightClientBuildFailure(Box<TmBuilderError>),
}
impl From<LightClientError> for Error {
fn from(e: LightClientError) -> Self {
Error::VerificationFailure(Box::new(e))
}
}
impl From<TmBuilderError> for Error {
fn from(e: TmBuilderError) -> Self {
Error::LightClientBuildFailure(Box::new(e))
}
}

View file

@ -0,0 +1,24 @@
#![no_std]
#![forbid(unsafe_code)]
#![warn(
clippy::checked_conversions,
clippy::panic,
clippy::panic_in_result_fn,
clippy::unwrap_used,
trivial_casts,
trivial_numeric_casts,
rust_2018_idioms,
unused_lifetimes,
unused_import_braces,
unused_qualifications
)]
extern crate alloc;
mod error;
mod null_io;
mod provider;
pub use error::Error;
pub use provider::make_provider;
pub use provider::StatelessProvider;

View file

@ -0,0 +1,13 @@
use tendermint_light_client::{
components::io::{AtHeight, Io, IoError},
types::LightBlock,
};
#[derive(Clone, Debug)]
pub struct NullIo;
impl Io for NullIo {
fn fetch_light_block(&self, _height: AtHeight) -> Result<LightBlock, IoError> {
unimplemented!("stateless verification does NOT need access to Io")
}
}

View file

@ -0,0 +1,97 @@
use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use tendermint::Hash;
use tendermint_light_client::{
builder::LightClientBuilder,
components::{clock::SystemClock, scheduler},
instance::Instance,
light_client::Options,
predicates::ProdPredicates,
store::{memory::MemoryStore, LightStore},
types::{Height, LightBlock, Status},
verifier::ProdVerifier,
};
use crate::error::Error;
use crate::null_io::NullIo;
/// A interface over a stateless light client instance.
#[derive(Debug)]
pub struct StatelessProvider {
#[allow(unused)]
chain_id: String,
instance: Instance,
}
impl StatelessProvider {
pub fn new(chain_id: String, instance: Instance) -> Self {
Self { chain_id, instance }
}
pub fn verify_to_height(&mut self, height: Height) -> Result<LightBlock, Error> {
self.instance
.light_client
.verify_to_target(height, &mut self.instance.state)
.map_err(Into::<Error>::into)
}
}
pub fn make_provider(
chain_id: &str,
trusted_height: Height,
trusted_hash: Hash,
trace: Vec<LightBlock>,
options: Options,
) -> Result<StatelessProvider, Error> {
// Make sure the trace is not empty and that the first light block corresponds to trusted
verify_trace_against_trusted(&trace, trusted_height, trusted_hash)?;
let mut light_store = Box::new(MemoryStore::new());
for light_block in &trace {
light_store.insert(light_block.clone(), Status::Unverified);
}
let node_id = trace[0].provider;
let instance = LightClientBuilder::custom(
node_id,
options,
light_store,
Box::new(NullIo {}),
Box::new(SystemClock),
#[allow(clippy::box_default)]
Box::new(ProdVerifier::default()),
Box::new(scheduler::basic_bisecting_schedule),
Box::new(ProdPredicates),
)
.trust_light_block(trace[0].clone())
.map_err(Into::<Error>::into)?
.build();
Ok(StatelessProvider::new(chain_id.to_string(), instance))
}
fn verify_trace_against_trusted(
trace: &[LightBlock],
trusted_height: Height,
trusted_hash: Hash,
) -> Result<(), Error> {
let Some(first_block) = trace.first() else {
return Err(Error::EmptyTrace);
};
let first_height = first_block.signed_header.header.height;
let first_hash = first_block.signed_header.header.hash();
if first_height != trusted_height || first_hash != trusted_hash {
return Err(Error::FirstTraceBlockNotTrusted {
expected: (first_height, first_hash),
found: (trusted_height, trusted_hash),
});
}
Ok(())
}