zebra_consensus/
block.rs

1//! Consensus-based block verification.
2//!
3//! In contrast to checkpoint verification, which only checks hardcoded
4//! hashes, block verification checks all Zcash consensus rules.
5//!
6//! The block verifier performs all of the semantic validation checks.
7//! If accepted, the block is sent to the state service for contextual
8//! verification, where it may be accepted or rejected.
9
10use std::{
11    collections::HashSet,
12    future::Future,
13    pin::Pin,
14    sync::Arc,
15    task::{Context, Poll},
16};
17
18use chrono::Utc;
19use futures::stream::FuturesUnordered;
20use futures_util::FutureExt;
21use thiserror::Error;
22use tower::{Service, ServiceExt};
23use tracing::Instrument;
24
25use zebra_chain::{
26    amount::Amount,
27    block,
28    parameters::{subsidy::SubsidyError, Network},
29    transaction, transparent,
30    work::equihash,
31};
32use zebra_state as zs;
33
34use crate::{error::*, transaction as tx, BoxError};
35
36pub mod check;
37pub mod request;
38pub mod subsidy;
39
40pub use request::Request;
41
42#[cfg(test)]
43mod tests;
44
45/// Asynchronous semantic block verification.
46#[derive(Debug)]
47pub struct SemanticBlockVerifier<S, V> {
48    /// The network to be verified.
49    network: Network,
50    state_service: S,
51    transaction_verifier: V,
52}
53
54/// Block verification errors.
55// TODO: dedupe with crate::error::BlockError
56#[non_exhaustive]
57#[allow(missing_docs)]
58#[derive(Debug, Error)]
59pub enum VerifyBlockError {
60    #[error("unable to verify depth for block {hash} from chain state during block verification")]
61    Depth { source: BoxError, hash: block::Hash },
62
63    #[error(transparent)]
64    Block {
65        #[from]
66        source: BlockError,
67    },
68
69    #[error(transparent)]
70    Equihash {
71        #[from]
72        source: equihash::Error,
73    },
74
75    #[error(transparent)]
76    Time(zebra_chain::block::BlockTimeError),
77
78    #[error("unable to commit block after semantic verification: {0}")]
79    // TODO: make this into a concrete type, and add it to is_duplicate_request() (#2908)
80    Commit(#[source] BoxError),
81
82    #[error("unable to validate block proposal: failed semantic verification (proof of work is not checked for proposals): {0}")]
83    // TODO: make this into a concrete type (see #5732)
84    ValidateProposal(#[source] BoxError),
85
86    #[error("invalid transaction: {0}")]
87    Transaction(#[from] TransactionError),
88
89    #[error("invalid block subsidy: {0}")]
90    Subsidy(#[from] SubsidyError),
91}
92
93impl VerifyBlockError {
94    /// Returns `true` if this is definitely a duplicate request.
95    /// Some duplicate requests might not be detected, and therefore return `false`.
96    pub fn is_duplicate_request(&self) -> bool {
97        match self {
98            VerifyBlockError::Block { source, .. } => source.is_duplicate_request(),
99            _ => false,
100        }
101    }
102
103    /// Returns a suggested misbehaviour score increment for a certain error.
104    pub fn misbehavior_score(&self) -> u32 {
105        // TODO: Adjust these values based on zcashd (#9258).
106        use VerifyBlockError::*;
107        match self {
108            Block { source } => source.misbehavior_score(),
109            Equihash { .. } => 100,
110            _other => 0,
111        }
112    }
113}
114
115/// The maximum allowed number of legacy signature check operations in a block.
116///
117/// This consensus rule is not documented, so Zebra follows the `zcashd` implementation.
118/// We re-use some `zcashd` C++ script code via `zebra-script` and `zcash_script`.
119///
120/// See:
121/// <https://github.com/zcash/zcash/blob/bad7f7eadbbb3466bebe3354266c7f69f607fcfd/src/consensus/consensus.h#L30>
122pub const MAX_BLOCK_SIGOPS: u32 = 20_000;
123
124impl<S, V> SemanticBlockVerifier<S, V>
125where
126    S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
127    S::Future: Send + 'static,
128    V: Service<tx::Request, Response = tx::Response, Error = BoxError> + Send + Clone + 'static,
129    V::Future: Send + 'static,
130{
131    /// Creates a new SemanticBlockVerifier
132    pub fn new(network: &Network, state_service: S, transaction_verifier: V) -> Self {
133        Self {
134            network: network.clone(),
135            state_service,
136            transaction_verifier,
137        }
138    }
139}
140
141impl<S, V> Service<Request> for SemanticBlockVerifier<S, V>
142where
143    S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
144    S::Future: Send + 'static,
145    V: Service<tx::Request, Response = tx::Response, Error = BoxError> + Send + Clone + 'static,
146    V::Future: Send + 'static,
147{
148    type Response = block::Hash;
149    type Error = VerifyBlockError;
150    type Future =
151        Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
152
153    fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
154        // We use the state for contextual verification, and we expect those
155        // queries to be fast. So we don't need to call
156        // `state_service.poll_ready()` here.
157        Poll::Ready(Ok(()))
158    }
159
160    fn call(&mut self, request: Request) -> Self::Future {
161        let mut state_service = self.state_service.clone();
162        let mut transaction_verifier = self.transaction_verifier.clone();
163        let network = self.network.clone();
164
165        let block = request.block();
166
167        // We don't include the block hash, because it's likely already in a parent span
168        let span = tracing::debug_span!("block", height = ?block.coinbase_height());
169
170        async move {
171            let hash = block.hash();
172            // Check that this block is actually a new block.
173            tracing::trace!("checking that block is not already in state");
174            match state_service
175                .ready()
176                .await
177                .map_err(|source| VerifyBlockError::Depth { source, hash })?
178                .call(zs::Request::KnownBlock(hash))
179                .await
180                .map_err(|source| VerifyBlockError::Depth { source, hash })?
181            {
182                zs::Response::KnownBlock(Some(location)) => {
183                    return Err(BlockError::AlreadyInChain(hash, location).into())
184                }
185                zs::Response::KnownBlock(None) => {}
186                _ => unreachable!("wrong response to Request::KnownBlock"),
187            }
188
189            tracing::trace!("performing block checks");
190            let height = block
191                .coinbase_height()
192                .ok_or(BlockError::MissingHeight(hash))?;
193
194            // Zebra does not support heights greater than
195            // [`block::Height::MAX`].
196            if height > block::Height::MAX {
197                Err(BlockError::MaxHeight(height, hash, block::Height::MAX))?;
198            }
199
200            // > The block data MUST be validated and checked against the server's usual
201            // > acceptance rules (excluding the check for a valid proof-of-work).
202            // <https://en.bitcoin.it/wiki/BIP_0023#Block_Proposal>
203            if request.is_proposal() || network.disable_pow() {
204                check::difficulty_threshold_is_valid(&block.header, &network, &height, &hash)?;
205            } else {
206                // Do the difficulty checks first, to raise the threshold for
207                // attacks that use any other fields.
208                check::difficulty_is_valid(&block.header, &network, &height, &hash)?;
209                check::equihash_solution_is_valid(&block.header)?;
210            }
211
212            // Next, check the Merkle root validity, to ensure that
213            // the header binds to the transactions in the blocks.
214
215            // Precomputing this avoids duplicating transaction hash computations.
216            let transaction_hashes: Arc<[_]> =
217                block.transactions.iter().map(|t| t.hash()).collect();
218
219            check::merkle_root_validity(&network, &block, &transaction_hashes)?;
220
221            // Since errors cause an early exit, try to do the
222            // quick checks first.
223
224            // Quick field validity and structure checks
225            let now = Utc::now();
226            check::time_is_valid_at(&block.header, now, &height, &hash)
227                .map_err(VerifyBlockError::Time)?;
228            let coinbase_tx = check::coinbase_is_first(&block)?;
229
230            let expected_block_subsidy =
231                zebra_chain::parameters::subsidy::block_subsidy(height, &network)?;
232
233            // See [ZIP-1015](https://zips.z.cash/zip-1015).
234            let deferred_pool_balance_change =
235                check::subsidy_is_valid(&block, &network, expected_block_subsidy)?;
236
237            // Now do the slower checks
238
239            // Check compatibility with ZIP-212 shielded Sapling and Orchard coinbase output decryption
240            tx::check::coinbase_outputs_are_decryptable(&coinbase_tx, &network, height)?;
241
242            // Send transactions to the transaction verifier to be checked
243            let mut async_checks = FuturesUnordered::new();
244
245            let known_utxos = Arc::new(transparent::new_ordered_outputs(
246                &block,
247                &transaction_hashes,
248            ));
249
250            let known_outpoint_hashes: Arc<HashSet<transaction::Hash>> =
251                Arc::new(known_utxos.keys().map(|outpoint| outpoint.hash).collect());
252
253            for (&transaction_hash, transaction) in
254                transaction_hashes.iter().zip(block.transactions.iter())
255            {
256                let rsp = transaction_verifier
257                    .ready()
258                    .await
259                    .expect("transaction verifier is always ready")
260                    .call(tx::Request::Block {
261                        transaction_hash,
262                        transaction: transaction.clone(),
263                        known_outpoint_hashes: known_outpoint_hashes.clone(),
264                        known_utxos: known_utxos.clone(),
265                        height,
266                        time: block.header.time,
267                    });
268                async_checks.push(rsp);
269            }
270            tracing::trace!(len = async_checks.len(), "built async tx checks");
271
272            // Get the transaction results back from the transaction verifier.
273
274            // Sum up some block totals from the transaction responses.
275            let mut sigops = 0;
276            let mut block_miner_fees = Ok(Amount::zero());
277
278            use futures::StreamExt;
279            while let Some(result) = async_checks.next().await {
280                tracing::trace!(?result, remaining = async_checks.len());
281                let response = result
282                    .map_err(Into::into)
283                    .map_err(VerifyBlockError::Transaction)?;
284
285                assert!(
286                    matches!(response, tx::Response::Block { .. }),
287                    "unexpected response from transaction verifier: {response:?}"
288                );
289
290                sigops += response.sigops();
291
292                // Coinbase transactions consume the miner fee,
293                // so they don't add any value to the block's total miner fee.
294                if let Some(miner_fee) = response.miner_fee() {
295                    block_miner_fees += miner_fee;
296                }
297            }
298
299            // Check the summed block totals
300
301            if sigops > MAX_BLOCK_SIGOPS {
302                Err(BlockError::TooManyTransparentSignatureOperations {
303                    height,
304                    hash,
305                    sigops,
306                })?;
307            }
308
309            let block_miner_fees =
310                block_miner_fees.map_err(|amount_error| BlockError::SummingMinerFees {
311                    height,
312                    hash,
313                    source: amount_error,
314                })?;
315
316            check::miner_fees_are_valid(
317                &coinbase_tx,
318                height,
319                block_miner_fees,
320                expected_block_subsidy,
321                deferred_pool_balance_change,
322                &network,
323            )?;
324
325            // Finally, submit the block for contextual verification.
326            let new_outputs = Arc::into_inner(known_utxos)
327                .expect("all verification tasks using known_utxos are complete");
328
329            let prepared_block = zs::SemanticallyVerifiedBlock {
330                block,
331                hash,
332                height,
333                new_outputs,
334                transaction_hashes,
335                deferred_pool_balance_change: Some(deferred_pool_balance_change),
336            };
337
338            // Return early for proposal requests.
339            if request.is_proposal() {
340                return match state_service
341                    .ready()
342                    .await
343                    .map_err(VerifyBlockError::ValidateProposal)?
344                    .call(zs::Request::CheckBlockProposalValidity(prepared_block))
345                    .await
346                    .map_err(VerifyBlockError::ValidateProposal)?
347                {
348                    zs::Response::ValidBlockProposal => Ok(hash),
349                    _ => unreachable!("wrong response for CheckBlockProposalValidity"),
350                };
351            }
352
353            match state_service
354                .ready()
355                .await
356                .map_err(VerifyBlockError::Commit)?
357                .call(zs::Request::CommitSemanticallyVerifiedBlock(prepared_block))
358                .await
359                .map_err(VerifyBlockError::Commit)?
360            {
361                zs::Response::Committed(committed_hash) => {
362                    assert_eq!(committed_hash, hash, "state must commit correct hash");
363                    Ok(hash)
364                }
365                _ => unreachable!("wrong response for CommitSemanticallyVerifiedBlock"),
366            }
367        }
368        .instrument(span)
369        .boxed()
370    }
371}