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