use core::fmt;
use std::{
future::Future,
mem,
pin::Pin,
task::{Context, Poll},
};
use futures::{future::BoxFuture, FutureExt};
use once_cell::sync::Lazy;
use rand::thread_rng;
use tokio::sync::watch;
use tower::{util::ServiceFn, Service};
use tower_batch_control::{Batch, BatchControl, RequestWeight};
use tower_fallback::Fallback;
use sapling_crypto::{bundle::Authorized, BatchValidator, Bundle};
use zakura_chain::transaction::SigHash;
use zcash_proofs::prover::LocalTxProver;
use zcash_protocol::value::ZatBalance;
use crate::{error::TransactionError, BoxError};
static SAPLING: Lazy<LocalTxProver> = Lazy::new(LocalTxProver::bundled);
pub fn sapling_prover() -> &'static LocalTxProver {
Lazy::force(&SAPLING)
}
#[derive(Clone)]
pub struct Item {
bundle: Bundle<Authorized, ZatBalance>,
sighash: SigHash,
}
impl Item {
pub fn new(bundle: Bundle<Authorized, ZatBalance>, sighash: SigHash) -> Self {
Self { bundle, sighash }
}
}
impl RequestWeight for Item {
fn request_weight(&self) -> usize {
self.bundle
.shielded_spends()
.len()
.saturating_add(self.bundle.shielded_outputs().len())
}
}
#[derive(Default)]
pub struct Verifier {
batch: BatchValidator,
tx: watch::Sender<Option<bool>>,
}
impl fmt::Debug for Verifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Verifier")
.field("batch", &"..")
.field("tx", &self.tx)
.finish()
}
}
impl Drop for Verifier {
fn drop(&mut self) {
let batch = mem::take(&mut self.batch);
let tx = mem::take(&mut self.tx);
rayon::spawn_fifo(move || {
let (spend_vk, output_vk) = SAPLING.verifying_keys();
let res = batch.validate(&spend_vk, &output_vk, thread_rng());
let _ = tx.send(Some(res));
});
}
}
impl Service<BatchControl<Item>> for Verifier {
type Response = ();
type Error = Box<dyn std::error::Error + Send + Sync>;
type Future = Pin<Box<dyn Future<Output = Result<(), Self::Error>> + Send>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: BatchControl<Item>) -> Self::Future {
match req {
BatchControl::Item(item) => {
let mut rx = self.tx.subscribe();
let bundle_check = self
.batch
.check_bundle(item.bundle, item.sighash.into())
.then_some(())
.ok_or(TransactionError::SaplingVerificationFailed);
async move {
bundle_check.map_err(BoxError::from)?;
rx.changed()
.await
.map_err(|_| BoxError::from("verifier was dropped without flushing"))?;
let is_valid = *rx.borrow().as_ref().ok_or_else(|| {
Box::<dyn std::error::Error + Send + Sync>::from(
"threadpool unexpectedly dropped channel sender",
)
})?;
if is_valid {
metrics::counter!("proofs.sapling.verified").increment(1);
Ok(())
} else {
metrics::counter!("proofs.sapling.invalid").increment(1);
Err(BoxError::from(TransactionError::SaplingVerificationFailed))
}
}
.boxed()
}
BatchControl::Flush => {
let batch = mem::take(&mut self.batch);
let tx = mem::take(&mut self.tx);
async move {
let start = std::time::Instant::now();
let spawn_result = tokio::task::spawn_blocking(move || {
let (spend_vk, output_vk) = SAPLING.verifying_keys();
batch.validate(&spend_vk, &output_vk, thread_rng())
})
.await;
let duration = start.elapsed().as_secs_f64();
let result_label = match &spawn_result {
Ok(true) => "success",
_ => "failure",
};
metrics::histogram!(
"zakura.consensus.batch.duration_seconds",
"verifier" => "groth16_sapling",
"result" => result_label
)
.record(duration);
let is_valid = spawn_result.as_ref().ok().copied();
let _ = tx.send(is_valid);
spawn_result.map(|_| ()).map_err(Self::Error::from)
}
.boxed()
}
}
}
}
pub fn verify_single(
item: Item,
) -> Pin<Box<dyn Future<Output = Result<(), Box<dyn std::error::Error + Send + Sync>>> + Send>> {
async move {
let mut verifier = Verifier::default();
let check = verifier
.batch
.check_bundle(item.bundle, item.sighash.into())
.then_some(())
.ok_or(TransactionError::SaplingVerificationFailed);
check.map_err(BoxError::from)?;
let is_valid = tokio::task::spawn_blocking(move || {
let (spend_vk, output_vk) = SAPLING.verifying_keys();
mem::take(&mut verifier.batch).validate(&spend_vk, &output_vk, thread_rng())
})
.await
.map_err(|_| BoxError::from("Sapling bundle validation thread panicked"))?;
if is_valid {
Ok(())
} else {
Err(BoxError::from(TransactionError::SaplingVerificationFailed))
}
}
.boxed()
}
pub static VERIFIER: Lazy<
Fallback<
Batch<Verifier, Item>,
ServiceFn<
fn(Item) -> BoxFuture<'static, Result<(), Box<dyn std::error::Error + Send + Sync>>>,
>,
>,
> = Lazy::new(|| {
Fallback::new(
Batch::new(
Verifier::default(),
super::MAX_BATCH_SIZE,
None,
super::MAX_BATCH_LATENCY,
),
tower::service_fn(verify_single),
)
});
#[cfg(test)]
mod tests {
use super::sapling_prover;
#[test]
fn sapling_prover_is_reused() {
assert!(std::ptr::eq(sapling_prover(), sapling_prover()));
}
}