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