cycles-quartz/crates/utils/tm-prover/src/prover.rs

234 lines
6.6 KiB
Rust
Raw Normal View History

#![deny(
// warnings,
trivial_casts,
trivial_numeric_casts,
unused_import_braces,
unused_qualifications
)]
#![forbid(unsafe_code)]
use std::time::Duration;
use color_eyre::{
eyre::{eyre, Result},
Report,
};
2024-10-01 14:27:57 +00:00
use futures::future::join_all;
use quartz_cw_proof::{
error::ProofError,
proof::{cw::CwProof, key::CwAbciKey, Proof},
};
use tendermint::{crypto::default::Sha256, evidence::Evidence, Hash};
use tendermint_light_client::{
builder::LightClientBuilder,
light_client::Options,
store::memory::MemoryStore,
types::{Height, LightBlock},
};
use tendermint_light_client_detector::{detect_divergence, Error, Provider, Trace};
use tendermint_rpc::{client::HttpClient, Client, HttpClientUrl};
use tracing::{error, info};
const WASM_STORE_KEY: &str = "/store/wasm/key";
use crate::config::{Config as TmProverConfig, ProofOutput};
pub async fn prove(
TmProverConfig {
chain_id,
primary,
witnesses,
trusted_height,
trusted_hash,
trust_threshold,
trusting_period,
max_clock_drift,
max_block_lag,
verbose: _,
contract_address,
storage_key,
}: TmProverConfig,
) -> Result<ProofOutput> {
let options = Options {
trust_threshold,
trusting_period: Duration::from_secs(trusting_period),
clock_drift: Duration::from_secs(max_clock_drift),
};
let mut provider = make_provider(
&chain_id,
primary.clone(),
trusted_height,
trusted_hash,
options,
)
.await?;
let client = HttpClient::builder(primary.clone()).build()?;
let trusted_block = provider
.latest_trusted()
.ok_or_else(|| eyre!("No trusted state found for primary"))?;
2024-08-28 23:45:09 +00:00
info!("Getting status of node");
let status = client.status().await?;
let latest_height = status.sync_info.latest_block_height;
let latest_app_hash = status.sync_info.latest_app_hash;
// `proof_height` is the height at which we want to query the blockchain's state
// This is one less than than the `latest_height` because we want to verify the merkle-proof for
// the state against the `app_hash` at `latest_height`.
// (because Tendermint commits to the latest `app_hash` in the subsequent block)
let proof_height = (latest_height.value() - 1)
.try_into()
.expect("infallible conversion");
info!("Verifying to latest height on primary...");
let primary_block = provider.verify_to_height(latest_height)?;
info!("Verified to height {} on primary", primary_block.height());
let mut primary_trace = provider.get_trace(primary_block.height());
let witnesses = join_all(witnesses.0.into_iter().map(|addr: HttpClientUrl| {
make_provider(
&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(max_clock_drift);
let max_block_lag = Duration::from_secs(max_block_lag);
run_detector(
&mut provider,
witnesses.as_mut_slice(),
primary_trace.clone(),
max_clock_drift,
max_block_lag,
)
.await?;
let path = WASM_STORE_KEY.to_owned();
let data = CwAbciKey::new(contract_address, storage_key, None);
let result = client
.abci_query(Some(path), data, Some(proof_height), true)
.await?;
let proof: CwProof = result
.clone()
.try_into()
.expect("result should contain proof");
proof
.verify(latest_app_hash.clone().into())
.map_err(|e: ProofError| eyre!(e))?;
// Replace the last block in the trace (i.e., the (latest - 1) block) with the latest block
// We don't actually verify the latest block because it will be verified on the other side
let latest_block = provider.fetch_light_block(latest_height)?;
let _ = primary_trace.pop();
primary_trace.push(latest_block);
let output = ProofOutput {
light_client_proof: primary_trace,
merkle_proof: proof.into(),
};
Ok(output)
}
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))
}