Extract tm-stateless-verifier crate
This commit is contained in:
parent
931c7b0d6f
commit
57d71ed391
6 changed files with 179 additions and 0 deletions
2
utils/tm-stateless-verifier/.gitignore
vendored
Normal file
2
utils/tm-stateless-verifier/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Cargo.lock
|
||||||
|
|
9
utils/tm-stateless-verifier/Cargo.toml
Normal file
9
utils/tm-stateless-verifier/Cargo.toml
Normal 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"] }
|
34
utils/tm-stateless-verifier/src/error.rs
Normal file
34
utils/tm-stateless-verifier/src/error.rs
Normal 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))
|
||||||
|
}
|
||||||
|
}
|
24
utils/tm-stateless-verifier/src/lib.rs
Normal file
24
utils/tm-stateless-verifier/src/lib.rs
Normal 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;
|
13
utils/tm-stateless-verifier/src/null_io.rs
Normal file
13
utils/tm-stateless-verifier/src/null_io.rs
Normal 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")
|
||||||
|
}
|
||||||
|
}
|
97
utils/tm-stateless-verifier/src/provider.rs
Normal file
97
utils/tm-stateless-verifier/src/provider.rs
Normal 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(())
|
||||||
|
}
|
Loading…
Reference in a new issue