2020-11-20 19:47:30 -08:00
|
|
|
use std::{collections::HashMap, future::Future, pin::Pin, sync::Arc};
|
2020-11-20 12:42:29 -08:00
|
|
|
|
2020-11-26 15:55:37 -08:00
|
|
|
use tower::timeout::Timeout;
|
2020-11-20 12:42:29 -08:00
|
|
|
use tracing::Instrument;
|
2020-10-14 14:06:32 -07:00
|
|
|
|
2020-11-24 12:30:58 -08:00
|
|
|
use zebra_chain::{parameters::NetworkUpgrade, transaction::Transaction, transparent};
|
2020-11-23 12:02:57 -08:00
|
|
|
use zebra_state::Utxo;
|
2020-10-14 14:06:32 -07:00
|
|
|
|
|
|
|
use crate::BoxError;
|
|
|
|
|
2020-11-26 15:55:37 -08:00
|
|
|
/// A timeout applied to UTXO lookup requests.
|
|
|
|
///
|
|
|
|
/// The exact value is non-essential, but this should be long enough to allow
|
|
|
|
/// out-of-order verification of blocks (UTXOs are not required to be ready
|
|
|
|
/// immediately) while being short enough to prune blocks that are too far in the
|
|
|
|
/// future to be worth keeping in the queue, and to fail blocks that reference
|
|
|
|
/// invalid UTXOs.
|
|
|
|
const UTXO_LOOKUP_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10 * 60);
|
|
|
|
|
2020-10-16 16:44:30 -07:00
|
|
|
/// Asynchronous script verification.
|
|
|
|
///
|
|
|
|
/// The verifier asynchronously requests the UTXO a transaction attempts
|
|
|
|
/// to use as an input, and verifies the script as soon as it becomes
|
|
|
|
/// available. This allows script verification to be performed
|
|
|
|
/// asynchronously, rather than requiring that the entire chain up to
|
|
|
|
/// the previous block is ready.
|
2020-06-09 21:09:11 -07:00
|
|
|
///
|
2020-10-16 16:44:30 -07:00
|
|
|
/// The asynchronous script verification design is documented in [RFC4].
|
|
|
|
///
|
|
|
|
/// [RFC4]: https://zebra.zfnd.org/dev/rfcs/0004-asynchronous-script-verification.html
|
2020-10-16 15:14:19 -07:00
|
|
|
#[derive(Debug, Clone)]
|
2020-10-14 14:06:32 -07:00
|
|
|
pub struct Verifier<ZS> {
|
2020-11-26 15:55:37 -08:00
|
|
|
state: Timeout<ZS>,
|
2020-10-14 14:06:32 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<ZS> Verifier<ZS> {
|
2020-11-24 12:30:58 -08:00
|
|
|
pub fn new(state: ZS) -> Self {
|
2020-11-26 15:55:37 -08:00
|
|
|
Self {
|
|
|
|
state: Timeout::new(state, UTXO_LOOKUP_TIMEOUT),
|
|
|
|
}
|
2020-10-14 14:06:32 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-16 16:44:30 -07:00
|
|
|
/// A script verification request.
|
2020-10-14 14:06:32 -07:00
|
|
|
#[derive(Debug)]
|
2020-10-16 15:14:19 -07:00
|
|
|
pub struct Request {
|
2020-11-24 12:30:58 -08:00
|
|
|
/// Ideally, this would supply only an `Outpoint` and the unlock script,
|
|
|
|
/// rather than the entire `Transaction`, but we call a C++
|
|
|
|
/// implementation, and its FFI requires the entire transaction.
|
|
|
|
///
|
|
|
|
/// This causes quadratic script verification behavior, so
|
|
|
|
/// at some future point, we need to reform this data.
|
2020-10-16 15:14:19 -07:00
|
|
|
pub transaction: Arc<Transaction>,
|
|
|
|
pub input_index: usize,
|
2020-11-24 12:30:58 -08:00
|
|
|
/// A set of additional UTXOs known in the context of this verification request.
|
|
|
|
///
|
|
|
|
/// This allows specifying additional UTXOs that are not already known to the chain state.
|
2020-11-23 12:02:57 -08:00
|
|
|
pub known_utxos: Arc<HashMap<transparent::OutPoint, Utxo>>,
|
2020-11-24 12:30:58 -08:00
|
|
|
/// The network upgrade active in the context of this verification request.
|
|
|
|
///
|
|
|
|
/// Because the consensus branch ID changes with each network upgrade,
|
|
|
|
/// it has to be specified on a per-request basis.
|
|
|
|
pub upgrade: NetworkUpgrade,
|
2020-10-14 14:06:32 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<ZS> tower::Service<Request> for Verifier<ZS>
|
|
|
|
where
|
|
|
|
ZS: tower::Service<zebra_state::Request, Response = zebra_state::Response, Error = BoxError>,
|
|
|
|
ZS::Future: Send + 'static,
|
|
|
|
{
|
|
|
|
type Response = ();
|
|
|
|
type Error = BoxError;
|
|
|
|
type Future =
|
|
|
|
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
|
|
|
|
|
|
|
|
fn poll_ready(
|
|
|
|
&mut self,
|
|
|
|
cx: &mut std::task::Context<'_>,
|
|
|
|
) -> std::task::Poll<Result<(), Self::Error>> {
|
|
|
|
self.state.poll_ready(cx)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn call(&mut self, req: Request) -> Self::Future {
|
|
|
|
use futures_util::FutureExt;
|
|
|
|
|
2020-11-20 19:47:30 -08:00
|
|
|
let Request {
|
|
|
|
transaction,
|
|
|
|
input_index,
|
|
|
|
known_utxos,
|
2020-11-24 12:30:58 -08:00
|
|
|
upgrade,
|
2020-11-20 19:47:30 -08:00
|
|
|
} = req;
|
|
|
|
let input = &transaction.inputs()[input_index];
|
2020-11-24 12:30:58 -08:00
|
|
|
let branch_id = upgrade
|
|
|
|
.branch_id()
|
|
|
|
.expect("post-Sapling NUs have a consensus branch ID");
|
2020-10-14 14:06:32 -07:00
|
|
|
|
|
|
|
match input {
|
|
|
|
transparent::Input::PrevOut { outpoint, .. } => {
|
2020-11-20 12:42:29 -08:00
|
|
|
let outpoint = *outpoint;
|
2020-10-14 14:06:32 -07:00
|
|
|
|
2020-11-20 12:42:29 -08:00
|
|
|
let span = tracing::trace_span!("script", ?outpoint);
|
2020-11-20 19:47:30 -08:00
|
|
|
let query =
|
2020-11-20 12:42:29 -08:00
|
|
|
span.in_scope(|| self.state.call(zebra_state::Request::AwaitUtxo(outpoint)));
|
|
|
|
|
2020-10-14 14:06:32 -07:00
|
|
|
async move {
|
2020-11-20 12:42:29 -08:00
|
|
|
tracing::trace!("awaiting outpoint lookup");
|
2020-11-23 12:02:57 -08:00
|
|
|
let utxo = if let Some(output) = known_utxos.get(&outpoint) {
|
2020-11-20 19:47:30 -08:00
|
|
|
tracing::trace!("UXTO in known_utxos, discarding query");
|
|
|
|
output.clone()
|
2020-11-23 12:02:57 -08:00
|
|
|
} else if let zebra_state::Response::Utxo(utxo) = query.await? {
|
|
|
|
utxo
|
2020-11-20 19:47:30 -08:00
|
|
|
} else {
|
|
|
|
unreachable!("AwaitUtxo always responds with Utxo")
|
2020-10-14 14:06:32 -07:00
|
|
|
};
|
2020-11-23 12:02:57 -08:00
|
|
|
tracing::trace!(?utxo, "got UTXO");
|
2020-10-14 14:06:32 -07:00
|
|
|
|
2020-11-24 01:31:03 -08:00
|
|
|
if transaction.inputs().len() < 20 {
|
|
|
|
zebra_script::is_valid(
|
|
|
|
transaction,
|
|
|
|
branch_id,
|
|
|
|
(input_index as u32, utxo.output),
|
|
|
|
)?;
|
|
|
|
tracing::trace!("script verification succeeded");
|
|
|
|
} else {
|
|
|
|
tracing::debug!(
|
|
|
|
inputs.len = transaction.inputs().len(),
|
|
|
|
"skipping verification of script with many inputs to avoid quadratic work until we fix zebra_script/zcash_script interface"
|
|
|
|
);
|
|
|
|
}
|
2020-10-14 14:06:32 -07:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2020-11-20 12:42:29 -08:00
|
|
|
.instrument(span)
|
2020-10-14 14:06:32 -07:00
|
|
|
.boxed()
|
|
|
|
}
|
|
|
|
transparent::Input::Coinbase { .. } => {
|
|
|
|
async { Err("unexpected coinbase input".into()) }.boxed()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|