Skip to main content

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 when attempting to commit a block after semantic verification.
79    #[error("unable to commit block after semantic verification: {0}")]
80    Commit(#[from] zs::CommitBlockError),
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    /// Errors originating from the state service, which may arise from general failures in interacting with the state.
93    /// This is for errors that are not specifically related to block depth or commit failures.
94    #[error("state service error for block {hash}: {source}")]
95    StateService { source: BoxError, hash: block::Hash },
96}
97
98impl VerifyBlockError {
99    /// Returns `true` if this is definitely a duplicate request.
100    /// Some duplicate requests might not be detected, and therefore return `false`.
101    pub fn is_duplicate_request(&self) -> bool {
102        match self {
103            VerifyBlockError::Block { source, .. } => source.is_duplicate_request(),
104            VerifyBlockError::Commit(commit_err) => commit_err.is_duplicate_request(),
105            _ => false,
106        }
107    }
108
109    /// Returns a suggested misbehaviour score increment for a certain error.
110    pub fn misbehavior_score(&self) -> u32 {
111        use VerifyBlockError::*;
112        match self {
113            Block { source } => source.misbehavior_score(),
114            Equihash { .. } | Subsidy(_) => 100,
115            Transaction(err) => err.mempool_misbehavior_score(),
116            Commit(err) => err.misbehavior_score(),
117            _other => 0,
118        }
119    }
120}
121
122/// The maximum number of transparent signature operations allowed in a block.
123///
124/// # Consensus
125///
126/// For every block, the sum of legacy and P2SH transparent signature operations across all
127/// transactions must not exceed [20_000].
128///
129/// ## Notes
130///
131/// This rule is inherited from pre-SegWit Bitcoin, and is not explicitly stated in the Zcash
132/// protocol spec. It is covered implicitly in [§7.6], which closes with "Other rules inherited from
133/// Bitcoin". The inclusion of this rule is tracked in [`zcash/zips#568`].
134///
135/// Zebra mirrors `zcashd`'s `ConnectBlock`, which sums `GetLegacySigOpCount()` and
136/// `GetP2SHSigOpCount()` per transaction before comparing against this constant.
137///
138/// [20_000]: <https://github.com/zcash/zcash/blob/bad7f7eadbbb3466bebe3354266c7f69f607fcfd/src/consensus/consensus.h#L30>
139/// [`zcash/zips#568`]: <https://github.com/zcash/zips/issues/568>
140/// [§7.6]: <https://zips.z.cash/protocol/protocol.pdf#blockheader>
141pub const MAX_BLOCK_SIGOPS: u32 = 20_000;
142
143impl<S, V> SemanticBlockVerifier<S, V>
144where
145    S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
146    S::Future: Send + 'static,
147    V: Service<tx::Request, Response = tx::Response, Error = BoxError> + Send + Clone + 'static,
148    V::Future: Send + 'static,
149{
150    /// Creates a new SemanticBlockVerifier
151    pub fn new(network: &Network, state_service: S, transaction_verifier: V) -> Self {
152        Self {
153            network: network.clone(),
154            state_service,
155            transaction_verifier,
156        }
157    }
158}
159
160impl<S, V> Service<Request> for SemanticBlockVerifier<S, V>
161where
162    S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
163    S::Future: Send + 'static,
164    V: Service<tx::Request, Response = tx::Response, Error = BoxError> + Send + Clone + 'static,
165    V::Future: Send + 'static,
166{
167    type Response = block::Hash;
168    type Error = VerifyBlockError;
169    type Future =
170        Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
171
172    fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
173        // We use the state for contextual verification, and we expect those
174        // queries to be fast. So we don't need to call
175        // `state_service.poll_ready()` here.
176        Poll::Ready(Ok(()))
177    }
178
179    fn call(&mut self, request: Request) -> Self::Future {
180        let mut state_service = self.state_service.clone();
181        let mut transaction_verifier = self.transaction_verifier.clone();
182        let network = self.network.clone();
183
184        let block = request.block();
185
186        // We don't include the block hash, because it's likely already in a parent span
187        let span = tracing::debug_span!("block", height = ?block.coinbase_height());
188
189        async move {
190            let hash = block.hash();
191            // Check that this block is actually a new block.
192            tracing::trace!("checking that block is not already in state");
193            match state_service
194                .ready()
195                .await
196                .map_err(|source| VerifyBlockError::Depth { source, hash })?
197                .call(zs::Request::KnownBlock(hash))
198                .await
199                .map_err(|source| VerifyBlockError::Depth { source, hash })?
200            {
201                zs::Response::KnownBlock(Some(location)) => {
202                    return Err(BlockError::AlreadyInChain(hash, location).into())
203                }
204                zs::Response::KnownBlock(None) => {}
205                _ => unreachable!("wrong response to Request::KnownBlock"),
206            }
207
208            tracing::trace!("performing block checks");
209            let height = block
210                .coinbase_height()
211                .ok_or(BlockError::MissingHeight(hash))?;
212
213            // Zebra does not support heights greater than
214            // [`block::Height::MAX`].
215            if height > block::Height::MAX {
216                Err(BlockError::MaxHeight(height, hash, block::Height::MAX))?;
217            }
218
219            // > The block data MUST be validated and checked against the server's usual
220            // > acceptance rules (excluding the check for a valid proof-of-work).
221            // <https://en.bitcoin.it/wiki/BIP_0023#Block_Proposal>
222            if request.is_proposal() || network.disable_pow() {
223                check::difficulty_threshold_is_valid(&block.header, &network, &height, &hash)?;
224            } else {
225                // Do the difficulty checks first, to raise the threshold for
226                // attacks that use any other fields.
227                check::difficulty_is_valid(&block.header, &network, &height, &hash)?;
228                check::equihash_solution_is_valid(&block.header)?;
229            }
230
231            // Next, check the Merkle root validity, to ensure that
232            // the header binds to the transactions in the blocks.
233
234            // Precomputing this avoids duplicating transaction hash computations.
235            let transaction_hashes: Arc<[_]> =
236                block.transactions.iter().map(|t| t.hash()).collect();
237
238            check::merkle_root_validity(&network, &block, &transaction_hashes)?;
239
240            // Since errors cause an early exit, try to do the
241            // quick checks first.
242
243            // Quick field validity and structure checks
244            let now = Utc::now();
245            check::time_is_valid_at(&block.header, now, &height, &hash)
246                .map_err(VerifyBlockError::Time)?;
247            let coinbase_tx = check::coinbase_is_first(&block)?;
248
249            let expected_block_subsidy =
250                zebra_chain::parameters::subsidy::block_subsidy(height, &network)?;
251
252            // See [ZIP-1015](https://zips.z.cash/zip-1015).
253            let deferred_pool_balance_change =
254                check::subsidy_is_valid(&block, &network, expected_block_subsidy)?;
255
256            // Now do the slower checks
257
258            // Check compatibility with ZIP-212 shielded Sapling and Orchard coinbase output decryption
259            tx::check::coinbase_outputs_are_decryptable(&coinbase_tx, &network, height)?;
260
261            // Send transactions to the transaction verifier to be checked
262            let mut async_checks = FuturesUnordered::new();
263
264            let known_utxos = Arc::new(transparent::new_ordered_outputs(
265                &block,
266                &transaction_hashes,
267            ));
268
269            let known_outpoint_hashes: Arc<HashSet<transaction::Hash>> =
270                Arc::new(known_utxos.keys().map(|outpoint| outpoint.hash).collect());
271
272            for (&transaction_hash, transaction) in
273                transaction_hashes.iter().zip(block.transactions.iter())
274            {
275                let rsp = transaction_verifier
276                    .ready()
277                    .await
278                    .expect("transaction verifier is always ready")
279                    .call(tx::Request::Block {
280                        transaction_hash,
281                        transaction: transaction.clone(),
282                        known_outpoint_hashes: known_outpoint_hashes.clone(),
283                        known_utxos: known_utxos.clone(),
284                        height,
285                        time: block.header.time,
286                    });
287                async_checks.push(rsp);
288            }
289            tracing::trace!(len = async_checks.len(), "built async tx checks");
290
291            // Get the transaction results back from the transaction verifier.
292
293            // Sum up some block totals from the transaction responses.
294            let mut sigops = 0;
295            let mut block_miner_fees = Ok(Amount::zero());
296
297            use futures::StreamExt;
298            while let Some(result) = async_checks.next().await {
299                tracing::trace!(?result, remaining = async_checks.len());
300                let response = result
301                    .map_err(Into::into)
302                    .map_err(VerifyBlockError::Transaction)?;
303
304                assert!(
305                    matches!(response, tx::Response::Block { .. }),
306                    "unexpected response from transaction verifier: {response:?}"
307                );
308
309                sigops += response.sigops();
310
311                // Coinbase transactions consume the miner fee,
312                // so they don't add any value to the block's total miner fee.
313                if let Some(miner_fee) = response.miner_fee() {
314                    block_miner_fees += miner_fee;
315                }
316            }
317
318            // Check the summed block totals
319
320            if sigops > MAX_BLOCK_SIGOPS {
321                Err(BlockError::TooManyTransparentSignatureOperations {
322                    height,
323                    hash,
324                    sigops,
325                })?;
326            }
327
328            let block_miner_fees =
329                block_miner_fees.map_err(|amount_error| BlockError::SummingMinerFees {
330                    height,
331                    hash,
332                    source: amount_error,
333                })?;
334
335            check::miner_fees_are_valid(
336                &coinbase_tx,
337                height,
338                block_miner_fees,
339                expected_block_subsidy,
340                deferred_pool_balance_change,
341                &network,
342            )?;
343
344            // Finally, submit the block for contextual verification.
345            let new_outputs = Arc::into_inner(known_utxos)
346                .expect("all verification tasks using known_utxos are complete");
347
348            let prepared_block = zs::SemanticallyVerifiedBlock {
349                block,
350                hash,
351                height,
352                new_outputs,
353                transaction_hashes,
354                deferred_pool_balance_change: Some(deferred_pool_balance_change),
355            };
356
357            // Return early for proposal requests.
358            if request.is_proposal() {
359                return match state_service
360                    .ready()
361                    .await
362                    .map_err(VerifyBlockError::ValidateProposal)?
363                    .call(zs::Request::CheckBlockProposalValidity(prepared_block))
364                    .await
365                    .map_err(VerifyBlockError::ValidateProposal)?
366                {
367                    zs::Response::ValidBlockProposal => Ok(hash),
368                    _ => unreachable!("wrong response for CheckBlockProposalValidity"),
369                };
370            }
371
372            match state_service
373                .ready()
374                .await
375                .map_err(|source| VerifyBlockError::StateService { source, hash })?
376                .call(zs::Request::CommitSemanticallyVerifiedBlock(prepared_block))
377                .await
378            {
379                Ok(zs::Response::Committed(committed_hash)) => {
380                    assert_eq!(committed_hash, hash, "state must commit correct hash");
381                    Ok(hash)
382                }
383
384                Err(source) => {
385                    if let Some(commit_err) = source.downcast_ref::<zs::CommitBlockError>() {
386                        return Err(VerifyBlockError::Commit(commit_err.clone()));
387                    }
388
389                    Err(VerifyBlockError::StateService { source, hash })
390                }
391
392                _ => unreachable!("wrong response for CommitSemanticallyVerifiedBlock"),
393            }
394        }
395        .instrument(span)
396        .boxed()
397    }
398}