Merge branch 'master' into hu55a1n1/4-tee-tm-light-client

This commit is contained in:
Shoaib Ahmed 2023-11-23 11:43:28 +01:00 committed by GitHub
commit 454fe6a33e
5 changed files with 2589 additions and 0 deletions

1
.gitignore vendored
View file

@ -3,3 +3,4 @@
*.manifest.sgx
.idea/
enclaves/tm/target/
utils/tm-prover/target/

2247
utils/tm-prover/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,20 @@
[package]
name = "tm-prover"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
tendermint = "0.34.0"
tendermint-rpc = { version = "0.34.0", features = ["http-client"] }
tendermint-light-client = "0.34.0"
tendermint-light-client-detector = "0.34.0"
clap = { version = "4.1.8", features = ["derive"] }
color-eyre = "0.6.2"
futures = "0.3.27"
serde_json = "1.0.94"
tokio = { version = "1.26.0", features = ["full"] }
tracing = "0.1.37"
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }

16
utils/tm-prover/README.md Normal file
View file

@ -0,0 +1,16 @@
# The Tendermint light client prover
Enables stateless light client verification by generating a light client proof (AKA verification trace) for a given
block height and trusted height/hash.
## Usage
```bash
cargo run -- --chain-id osmosis-1 \
--primary https://rpc.osmosis.zone \
--witnesses https://rpc.osmosis.zone \
--trusted-height 12230413 \
--trusted-hash D3742DD1573436AF972472135A24B31D5ACE9A2C04791A76196F875955B90F1D \
--height 12230423 \
--trace-file light-client-proof.json
```

305
utils/tm-prover/src/main.rs Normal file
View file

@ -0,0 +1,305 @@
#![deny(
warnings,
trivial_casts,
trivial_numeric_casts,
unused_import_braces,
unused_qualifications
)]
#![forbid(unsafe_code)]
use std::{
fs::File,
io::{BufWriter, Write},
path::PathBuf,
str::FromStr,
time::Duration,
};
use clap::Parser;
use color_eyre::{
eyre::{eyre, Result},
Report,
};
use futures::future::join_all;
use tendermint::{crypto::default::Sha256, evidence::Evidence};
use tendermint_light_client::{
builder::LightClientBuilder,
light_client::Options,
store::memory::MemoryStore,
types::{Hash, Height, LightBlock, TrustThreshold},
};
use tendermint_light_client_detector::{detect_divergence, Error, Provider, Trace};
use tendermint_rpc::{Client, HttpClient, HttpClientUrl};
use tracing::{error, info, metadata::LevelFilter};
use tracing_subscriber::{util::SubscriberInitExt, EnvFilter};
fn parse_trust_threshold(s: &str) -> Result<TrustThreshold> {
if let Some((l, r)) = s.split_once('/') {
TrustThreshold::new(l.parse()?, r.parse()?).map_err(Into::into)
} else {
Err(eyre!(
"invalid trust threshold: {s}, format must be X/Y where X and Y are integers"
))
}
}
#[derive(Clone, Debug)]
struct List<T>(Vec<T>);
impl<E, T: FromStr<Err = E>> FromStr for List<T> {
type Err = E;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.split(',')
.map(|s| s.parse())
.collect::<Result<Vec<_>, _>>()
.map(Self)
}
}
#[derive(clap::Args, Debug, Clone)]
struct Verbosity {
/// Increase verbosity, can be repeated up to 2 times
#[arg(long, short, action = clap::ArgAction::Count)]
verbose: u8,
}
impl Verbosity {
fn to_level_filter(&self) -> LevelFilter {
match self.verbose {
0 => LevelFilter::INFO,
1 => LevelFilter::DEBUG,
_ => LevelFilter::TRACE,
}
}
}
#[derive(Debug, Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
/// Identifier of the chain
#[clap(long)]
chain_id: String,
/// Primary RPC address
#[clap(long)]
primary: HttpClientUrl,
/// Comma-separated list of witnesses RPC addresses
#[clap(long)]
witnesses: List<HttpClientUrl>,
/// Height of trusted header
#[clap(long)]
trusted_height: Height,
/// Hash of trusted header
#[clap(long)]
trusted_hash: Hash,
/// Height of the header to verify
#[clap(long)]
height: Option<Height>,
/// Trust threshold
#[clap(long, value_parser = parse_trust_threshold, default_value_t = TrustThreshold::TWO_THIRDS)]
trust_threshold: TrustThreshold,
/// Trusting period, in seconds (default: two weeks)
#[clap(long, default_value = "1209600")]
trusting_period: u64,
/// Maximum clock drift, in seconds
#[clap(long, default_value = "5")]
max_clock_drift: u64,
/// Maximum block lag, in seconds
#[clap(long, default_value = "5")]
max_block_lag: u64,
/// Output file to store light client proof (AKA verification trace)
#[clap(long)]
trace_file: Option<PathBuf>,
/// Increase verbosity
#[clap(flatten)]
verbose: Verbosity,
}
#[tokio::main]
async fn main() -> Result<()> {
color_eyre::install()?;
let args = Cli::parse();
let env_filter = EnvFilter::builder()
.with_default_directive(args.verbose.to_level_filter().into())
.from_env_lossy();
tracing_subscriber::fmt()
.with_target(false)
.with_env_filter(env_filter)
.finish()
.init();
let options = Options {
trust_threshold: args.trust_threshold,
trusting_period: Duration::from_secs(args.trusting_period),
clock_drift: Duration::from_secs(args.max_clock_drift),
};
let mut primary = make_provider(
&args.chain_id,
args.primary,
args.trusted_height,
args.trusted_hash,
options,
)
.await?;
let trusted_block = primary
.latest_trusted()
.ok_or_else(|| eyre!("No trusted state found for primary"))?;
let primary_block = if let Some(target_height) = args.height {
info!("Verifying to height {} on primary...", target_height);
primary.verify_to_height(target_height)
} else {
info!("Verifying to latest height on primary...");
primary.verify_to_highest()
}?;
info!("Verified to height {} on primary", primary_block.height());
let primary_trace = primary.get_trace(primary_block.height());
let witnesses = join_all(args.witnesses.0.into_iter().map(|addr| {
make_provider(
&args.chain_id,
addr,
trusted_block.height(),
trusted_block.signed_header.header.hash(),
options,
)
}))
.await;
let mut witnesses = witnesses.into_iter().collect::<Result<Vec<_>>>()?;
let max_clock_drift = Duration::from_secs(args.max_clock_drift);
let max_block_lag = Duration::from_secs(args.max_block_lag);
run_detector(
&mut primary,
witnesses.as_mut_slice(),
primary_trace.clone(),
max_clock_drift,
max_block_lag,
)
.await?;
if let Some(trace_file) = args.trace_file {
write_trace_to_file(trace_file, primary_trace).await?;
};
Ok(())
}
async fn write_trace_to_file(trace_file: PathBuf, output: Vec<LightBlock>) -> Result<()> {
info!("Writing proof to output file ({})", trace_file.display());
let file = File::create(trace_file)?;
let mut writer = BufWriter::new(file);
serde_json::to_writer(&mut writer, &output)?;
writer.flush()?;
Ok(())
}
async fn run_detector(
primary: &mut Provider,
witnesses: &mut [Provider],
primary_trace: Vec<LightBlock>,
max_clock_drift: Duration,
max_block_lag: Duration,
) -> Result<(), Report> {
if witnesses.is_empty() {
return Err(Error::no_witnesses().into());
}
info!(
"Running misbehavior detection against {} witnesses...",
witnesses.len()
);
let primary_trace = Trace::new(primary_trace)?;
for witness in witnesses {
let divergence = detect_divergence::<Sha256>(
Some(primary),
witness,
primary_trace.clone().into_vec(),
max_clock_drift,
max_block_lag,
)
.await;
let evidence = match divergence {
Ok(Some(divergence)) => divergence.evidence,
Ok(None) => {
info!(
"no divergence found between primary and witness {}",
witness.peer_id()
);
continue;
}
Err(e) => {
error!(
"failed to run attack detector against witness {}: {e}",
witness.peer_id()
);
continue;
}
};
// Report the evidence to the witness
witness
.report_evidence(Evidence::from(evidence.against_primary))
.await
.map_err(|e| eyre!("failed to report evidence to witness: {}", e))?;
if let Some(against_witness) = evidence.against_witness {
// Report the evidence to the primary
primary
.report_evidence(Evidence::from(against_witness))
.await
.map_err(|e| eyre!("failed to report evidence to primary: {}", e))?;
}
}
Ok(())
}
async fn make_provider(
chain_id: &str,
rpc_addr: HttpClientUrl,
trusted_height: Height,
trusted_hash: Hash,
options: Options,
) -> Result<Provider> {
use tendermint_rpc::client::CompatMode;
let rpc_client = HttpClient::builder(rpc_addr)
.compat_mode(CompatMode::V0_34)
.build()?;
let node_id = rpc_client.status().await?.node_info.id;
let light_store = Box::new(MemoryStore::new());
let instance =
LightClientBuilder::prod(node_id, rpc_client.clone(), light_store, options, None)
.trust_primary_at(trusted_height, trusted_hash)?
.build();
Ok(Provider::new(chain_id.to_string(), instance, rpc_client))
}