Skip to main content

zebra_consensus/
script.rs

1use std::{future::Future, pin::Pin, sync::Arc};
2
3use tracing::Instrument;
4
5use zebra_chain::transparent;
6use zebra_script::CachedFfiTransaction;
7
8use crate::BoxError;
9
10#[cfg(test)]
11mod tests;
12
13/// Asynchronous script verification.
14///
15/// The verifier asynchronously requests the UTXO a transaction attempts
16/// to use as an input, and verifies the script as soon as it becomes
17/// available.  This allows script verification to be performed
18/// asynchronously, rather than requiring that the entire chain up to
19/// the previous block is ready.
20///
21/// The asynchronous script verification design is documented in [RFC4].
22///
23/// [RFC4]: https://zebra.zfnd.org/dev/rfcs/0004-asynchronous-script-verification.html
24#[derive(Debug, Clone, Default, Copy, PartialEq, Eq)]
25pub struct Verifier;
26
27/// A script verification request.
28#[derive(Debug)]
29pub struct Request {
30    /// A cached transaction, in the format required by the script verifier FFI interface.
31    pub cached_ffi_transaction: Arc<CachedFfiTransaction>,
32    /// The index of an input in `cached_ffi_transaction`, used for verifying this request
33    ///
34    /// Coinbase inputs are rejected by the script verifier, because they do not spend a UTXO.
35    pub input_index: usize,
36}
37
38impl tower::Service<Request> for Verifier {
39    type Response = ();
40    type Error = BoxError;
41    type Future =
42        Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
43
44    fn poll_ready(
45        &mut self,
46        _cx: &mut std::task::Context<'_>,
47    ) -> std::task::Poll<Result<(), Self::Error>> {
48        std::task::Poll::Ready(Ok(()))
49    }
50
51    fn call(&mut self, req: Request) -> Self::Future {
52        use futures_util::FutureExt;
53
54        let Request {
55            cached_ffi_transaction,
56            input_index,
57        } = req;
58
59        let span = tracing::trace_span!("script");
60        async move {
61            let input = &cached_ffi_transaction
62                .inputs()
63                .get(input_index)
64                .ok_or_else(|| {
65                    format!("cached_ffi_transaction missing input at index {input_index}")
66                })?;
67
68            match input {
69                transparent::Input::PrevOut { outpoint, .. } => {
70                    let outpoint = *outpoint;
71
72                    // Avoid calling the state service if the utxo is already known
73                    cached_ffi_transaction.is_valid(input_index)?;
74                    tracing::trace!(?outpoint, "script verification succeeded");
75
76                    Ok(())
77                }
78                transparent::Input::Coinbase { .. } => Err("unexpected coinbase input".into()),
79            }
80        }
81        .instrument(span)
82        .boxed()
83    }
84}