From 57d71ed3910d372760392651955dc5be330f5565 Mon Sep 17 00:00:00 2001 From: hu55a1n1 Date: Sun, 31 Dec 2023 13:12:32 -0800 Subject: [PATCH] Extract tm-stateless-verifier crate --- utils/tm-stateless-verifier/.gitignore | 2 + utils/tm-stateless-verifier/Cargo.toml | 9 ++ utils/tm-stateless-verifier/src/error.rs | 34 ++++++++ utils/tm-stateless-verifier/src/lib.rs | 24 +++++ utils/tm-stateless-verifier/src/null_io.rs | 13 +++ utils/tm-stateless-verifier/src/provider.rs | 97 +++++++++++++++++++++ 6 files changed, 179 insertions(+) create mode 100644 utils/tm-stateless-verifier/.gitignore create mode 100644 utils/tm-stateless-verifier/Cargo.toml create mode 100644 utils/tm-stateless-verifier/src/error.rs create mode 100644 utils/tm-stateless-verifier/src/lib.rs create mode 100644 utils/tm-stateless-verifier/src/null_io.rs create mode 100644 utils/tm-stateless-verifier/src/provider.rs diff --git a/utils/tm-stateless-verifier/.gitignore b/utils/tm-stateless-verifier/.gitignore new file mode 100644 index 0000000..a93fe28 --- /dev/null +++ b/utils/tm-stateless-verifier/.gitignore @@ -0,0 +1,2 @@ +Cargo.lock + diff --git a/utils/tm-stateless-verifier/Cargo.toml b/utils/tm-stateless-verifier/Cargo.toml new file mode 100644 index 0000000..3bb69ec --- /dev/null +++ b/utils/tm-stateless-verifier/Cargo.toml @@ -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"] } diff --git a/utils/tm-stateless-verifier/src/error.rs b/utils/tm-stateless-verifier/src/error.rs new file mode 100644 index 0000000..1235e34 --- /dev/null +++ b/utils/tm-stateless-verifier/src/error.rs @@ -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), + /// failed to build light client (`{0}`) + LightClientBuildFailure(Box), +} + +impl From for Error { + fn from(e: LightClientError) -> Self { + Error::VerificationFailure(Box::new(e)) + } +} + +impl From for Error { + fn from(e: TmBuilderError) -> Self { + Error::LightClientBuildFailure(Box::new(e)) + } +} diff --git a/utils/tm-stateless-verifier/src/lib.rs b/utils/tm-stateless-verifier/src/lib.rs new file mode 100644 index 0000000..7f3d554 --- /dev/null +++ b/utils/tm-stateless-verifier/src/lib.rs @@ -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; diff --git a/utils/tm-stateless-verifier/src/null_io.rs b/utils/tm-stateless-verifier/src/null_io.rs new file mode 100644 index 0000000..9079e4c --- /dev/null +++ b/utils/tm-stateless-verifier/src/null_io.rs @@ -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 { + unimplemented!("stateless verification does NOT need access to Io") + } +} diff --git a/utils/tm-stateless-verifier/src/provider.rs b/utils/tm-stateless-verifier/src/provider.rs new file mode 100644 index 0000000..9d8bedd --- /dev/null +++ b/utils/tm-stateless-verifier/src/provider.rs @@ -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 { + self.instance + .light_client + .verify_to_target(height, &mut self.instance.state) + .map_err(Into::::into) + } +} + +pub fn make_provider( + chain_id: &str, + trusted_height: Height, + trusted_hash: Hash, + trace: Vec, + options: Options, +) -> Result { + // 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::::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(()) +}