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