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