use std::{
fmt,
future::Future,
mem,
pin::Pin,
task::{Context, Poll},
};
use futures::{future::BoxFuture, FutureExt};
use once_cell::sync::Lazy;
use orchard::{
bundle::{BatchError, BatchValidator},
circuit::{OrchardCircuitVersion, VerifyingKey},
};
use rand::thread_rng;
use zakura_chain::{parameters::NetworkUpgrade, transaction::SigHash};
use zcash_protocol::value::ZatBalance;
use crate::{error::TransactionError, BoxError};
use thiserror::Error;
use tokio::sync::watch;
use tower::Service;
use tower_batch_control::{Batch, BatchControl, RequestWeight};
use tower_fallback::Fallback;
use super::spawn_fifo;
#[cfg(test)]
mod tests;
const HALO2_MAX_BATCH_SIZE: usize = super::MAX_BATCH_SIZE;
type VerifyResult = bool;
type Sender = watch::Sender<Option<VerifyResult>>;
pub type ItemVerifyingKey = VerifyingKey;
lazy_static::lazy_static! {
pub static ref VERIFYING_KEY_PRE_NU6_2: ItemVerifyingKey =
ItemVerifyingKey::build(OrchardCircuitVersion::InsecurePreNu6_2);
pub static ref VERIFYING_KEY_NU6_2: ItemVerifyingKey =
ItemVerifyingKey::build(OrchardCircuitVersion::FixedPostNu6_2);
pub static ref VERIFYING_KEY_NU6_3_ONWARD: ItemVerifyingKey =
ItemVerifyingKey::build(OrchardCircuitVersion::PostNu6_3);
}
#[derive(Clone, Debug)]
pub struct Item {
bundle: orchard::bundle::Bundle<orchard::bundle::Authorized, ZatBalance>,
sighash: SigHash,
}
impl RequestWeight for Item {
fn request_weight(&self) -> usize {
self.bundle.actions().len()
}
}
impl Item {
pub fn new(
bundle: orchard::bundle::Bundle<orchard::bundle::Authorized, ZatBalance>,
sighash: SigHash,
) -> Self {
Self { bundle, sighash }
}
pub fn verify_single(self, vk: &ItemVerifyingKey) -> bool {
let mut batch = BatchValidator::new(vk);
if batch.queue(self).is_err() {
return false;
}
batch.validate(thread_rng())
}
}
trait QueueBatchVerify {
fn queue(&mut self, item: Item) -> Result<(), BatchError>;
}
impl QueueBatchVerify for BatchValidator<'_> {
fn queue(&mut self, Item { bundle, sighash }: Item) -> Result<(), BatchError> {
self.add_bundle(&bundle, sighash.0)
}
}
#[derive(Clone, Debug, Error, Eq, PartialEq)]
#[allow(missing_docs)]
pub enum Halo2Error {
#[error("the constraint system is not satisfied")]
ConstraintSystemFailure,
#[error("unknown Halo2 error")]
Other,
}
impl From<halo2::plonk::Error> for Halo2Error {
fn from(err: halo2::plonk::Error) -> Halo2Error {
match err {
halo2::plonk::Error::ConstraintSystemFailure => Halo2Error::ConstraintSystemFailure,
_ => Halo2Error::Other,
}
}
}
#[derive(Clone, Copy)]
pub struct OrchardFallback {
vk: &'static ItemVerifyingKey,
}
impl Service<Item> for OrchardFallback {
type Response = ();
type Error = BoxError;
type Future = BoxFuture<'static, Result<(), BoxError>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, item: Item) -> Self::Future {
Verifier::verify_single_spawning(item, self.vk).boxed()
}
}
type VerifierService = Fallback<Batch<Verifier, Item>, OrchardFallback>;
fn batch_verifier(vk: &'static ItemVerifyingKey) -> VerifierService {
Fallback::new(
Batch::new(
Verifier::new(vk),
HALO2_MAX_BATCH_SIZE,
None,
super::MAX_BATCH_LATENCY,
),
OrchardFallback { vk },
)
}
pub static VERIFIER_PRE_NU6_2: Lazy<VerifierService> =
Lazy::new(|| batch_verifier(&VERIFYING_KEY_PRE_NU6_2));
pub static VERIFIER_NU6_2: Lazy<VerifierService> =
Lazy::new(|| batch_verifier(&VERIFYING_KEY_NU6_2));
pub static VERIFIER_NU6_3_ONWARD: Lazy<VerifierService> =
Lazy::new(|| batch_verifier(&VERIFYING_KEY_NU6_3_ONWARD));
fn lazy_verifier_for(network_upgrade: NetworkUpgrade) -> &'static Lazy<VerifierService> {
use NetworkUpgrade::*;
match network_upgrade {
Genesis | BeforeOverwinter | Overwinter | Sapling | Blossom | Heartwood | Canopy | Nu5
| Nu6 | Nu6_1 => &VERIFIER_PRE_NU6_2,
Nu6_2 => &VERIFIER_NU6_2,
Nu6_3 | Nu7 => &VERIFIER_NU6_3_ONWARD,
#[cfg(zcash_unstable = "zfuture")]
ZFuture => &VERIFIER_NU6_3_ONWARD,
}
}
pub fn verifier_for(network_upgrade: NetworkUpgrade) -> &'static VerifierService {
lazy_verifier_for(network_upgrade)
}
pub struct Verifier {
vk: &'static ItemVerifyingKey,
batch: BatchValidator<'static>,
tx: Sender,
}
impl Verifier {
fn new(vk: &'static ItemVerifyingKey) -> Self {
let (tx, _) = watch::channel(None);
Self {
vk,
batch: BatchValidator::new(vk),
tx,
}
}
fn take(&mut self) -> (BatchValidator<'static>, Sender) {
let batch = mem::replace(&mut self.batch, BatchValidator::new(self.vk));
let (tx, _) = watch::channel(None);
let tx = mem::replace(&mut self.tx, tx);
(batch, tx)
}
fn verify(batch: BatchValidator<'static>, tx: Sender) {
let result = batch.validate(thread_rng());
let _ = tx.send(Some(result));
}
fn flush_blocking(&mut self) {
let (batch, tx) = self.take();
tokio::task::block_in_place(|| rayon::spawn_fifo(move || Self::verify(batch, tx)));
}
async fn flush_spawning(batch: BatchValidator<'static>, tx: Sender) {
let start = std::time::Instant::now();
let result = spawn_fifo(move || batch.validate(thread_rng())).await;
let duration = start.elapsed().as_secs_f64();
let result_label = match &result {
Ok(true) => "success",
_ => "failure",
};
metrics::histogram!(
"zakura.consensus.batch.duration_seconds",
"verifier" => "halo2",
"result" => result_label
)
.record(duration);
let _ = tx.send(result.ok());
}
async fn verify_single_spawning(
item: Item,
vk: &'static ItemVerifyingKey,
) -> Result<(), BoxError> {
if spawn_fifo(move || item.verify_single(vk)).await? {
Ok(())
} else {
Err(TransactionError::Halo2VerificationFailed.into())
}
}
}
impl fmt::Debug for Verifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = "Verifier";
f.debug_struct(name).field("batch", &"..").finish()
}
}
impl Service<BatchControl<Item>> for Verifier {
type Response = ();
type Error = BoxError;
type Future = Pin<Box<dyn Future<Output = Result<(), BoxError>> + Send + 'static>>;
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) => {
tracing::trace!("got item");
if let Err(err) = self.batch.queue(item) {
return Box::pin(async move { Err(BoxError::from(err)) });
}
let mut rx = self.tx.subscribe();
Box::pin(async move {
match rx.changed().await {
Ok(()) => {
let is_valid = *rx
.borrow()
.as_ref()
.ok_or("threadpool unexpectedly dropped response channel sender. Is Zakura shutting down?")?;
if is_valid {
tracing::trace!(?is_valid, "verified halo2 proof");
metrics::counter!("proofs.halo2.verified").increment(1);
Ok(())
} else {
tracing::trace!(?is_valid, "invalid halo2 proof");
metrics::counter!("proofs.halo2.invalid").increment(1);
Err(TransactionError::Halo2VerificationFailed.into())
}
}
Err(_recv_error) => panic!("verifier was dropped without flushing"),
}
})
}
BatchControl::Flush => {
tracing::trace!("got halo2 flush command");
let (batch, tx) = self.take();
Box::pin(Self::flush_spawning(batch, tx).map(|()| Ok(())))
}
}
}
}
impl Drop for Verifier {
fn drop(&mut self) {
self.flush_blocking()
}
}