1use 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#[derive(Debug)]
47pub struct SemanticBlockVerifier<S, V> {
48 network: Network,
50 state_service: S,
51 transaction_verifier: V,
52}
53
54#[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}")]
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 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 #[error("state service error for block {hash}: {source}")]
95 StateService { source: BoxError, hash: block::Hash },
96}
97
98impl VerifyBlockError {
99 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 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
122fn map_commit_error(source: BoxError, hash: block::Hash) -> VerifyBlockError {
130 if let Some(commit_err) = source
131 .downcast_ref::<zs::CommitSemanticallyVerifiedError>()
132 .map(zs::CommitSemanticallyVerifiedError::inner)
133 .or_else(|| source.downcast_ref::<zs::CommitBlockError>())
134 {
135 return VerifyBlockError::Commit(commit_err.clone());
136 }
137
138 VerifyBlockError::StateService { source, hash }
139}
140
141pub const MAX_BLOCK_SIGOPS: u32 = 20_000;
161
162impl<S, V> SemanticBlockVerifier<S, V>
163where
164 S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
165 S::Future: Send + 'static,
166 V: Service<tx::Request, Response = tx::Response, Error = BoxError> + Send + Clone + 'static,
167 V::Future: Send + 'static,
168{
169 pub fn new(network: &Network, state_service: S, transaction_verifier: V) -> Self {
171 Self {
172 network: network.clone(),
173 state_service,
174 transaction_verifier,
175 }
176 }
177}
178
179impl<S, V> Service<Request> for SemanticBlockVerifier<S, V>
180where
181 S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
182 S::Future: Send + 'static,
183 V: Service<tx::Request, Response = tx::Response, Error = BoxError> + Send + Clone + 'static,
184 V::Future: Send + 'static,
185{
186 type Response = block::Hash;
187 type Error = VerifyBlockError;
188 type Future =
189 Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
190
191 fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
192 Poll::Ready(Ok(()))
196 }
197
198 fn call(&mut self, request: Request) -> Self::Future {
199 let mut state_service = self.state_service.clone();
200 let mut transaction_verifier = self.transaction_verifier.clone();
201 let network = self.network.clone();
202
203 let block = request.block();
204
205 let span = tracing::debug_span!("block", height = ?block.coinbase_height());
207
208 async move {
209 let hash = block.hash();
210 tracing::trace!("checking that block is not already in state");
212 match state_service
213 .ready()
214 .await
215 .map_err(|source| VerifyBlockError::Depth { source, hash })?
216 .call(zs::Request::KnownBlock(hash))
217 .await
218 .map_err(|source| VerifyBlockError::Depth { source, hash })?
219 {
220 zs::Response::KnownBlock(Some(location)) => {
221 return Err(BlockError::AlreadyInChain(hash, location).into())
222 }
223 zs::Response::KnownBlock(None) => {}
224 _ => unreachable!("wrong response to Request::KnownBlock"),
225 }
226
227 tracing::trace!("performing block checks");
228 let height = block
229 .coinbase_height()
230 .ok_or(BlockError::MissingHeight(hash))?;
231
232 if height > block::Height::MAX {
235 Err(BlockError::MaxHeight(height, hash, block::Height::MAX))?;
236 }
237
238 if request.is_proposal() || network.disable_pow() {
242 check::difficulty_threshold_is_valid(&block.header, &network, &height, &hash)?;
243 } else {
244 check::difficulty_is_valid(&block.header, &network, &height, &hash)?;
247 check::equihash_solution_is_valid(&block.header)?;
248 }
249
250 let transaction_hashes: Arc<[_]> =
255 block.transactions.iter().map(|t| t.hash()).collect();
256
257 check::merkle_root_validity(&network, &block, &transaction_hashes)?;
258
259 let now = Utc::now();
264 check::time_is_valid_at(&block.header, now, &height, &hash)
265 .map_err(VerifyBlockError::Time)?;
266 let coinbase_tx = check::coinbase_is_first(&block)?;
267
268 let expected_block_subsidy =
269 zebra_chain::parameters::subsidy::block_subsidy(height, &network)?;
270
271 let deferred_pool_balance_change =
273 check::subsidy_is_valid(&block, &network, expected_block_subsidy)?;
274
275 tx::check::coinbase_outputs_are_decryptable(&coinbase_tx, &network, height)?;
279
280 let mut async_checks = FuturesUnordered::new();
282
283 let known_utxos = Arc::new(transparent::new_ordered_outputs(
284 &block,
285 &transaction_hashes,
286 ));
287
288 let known_outpoint_hashes: Arc<HashSet<transaction::Hash>> =
289 Arc::new(known_utxos.keys().map(|outpoint| outpoint.hash).collect());
290
291 for (&transaction_hash, transaction) in
292 transaction_hashes.iter().zip(block.transactions.iter())
293 {
294 let rsp = transaction_verifier
295 .ready()
296 .await
297 .expect("transaction verifier is always ready")
298 .call(tx::Request::Block {
299 transaction_hash,
300 transaction: transaction.clone(),
301 known_outpoint_hashes: known_outpoint_hashes.clone(),
302 known_utxos: known_utxos.clone(),
303 height,
304 time: block.header.time,
305 });
306 async_checks.push(rsp);
307 }
308 tracing::trace!(len = async_checks.len(), "built async tx checks");
309
310 let mut sigops = 0;
314 let mut block_miner_fees = Ok(Amount::zero());
315
316 use futures::StreamExt;
317 while let Some(result) = async_checks.next().await {
318 tracing::trace!(?result, remaining = async_checks.len());
319 let response = result
320 .map_err(Into::into)
321 .map_err(VerifyBlockError::Transaction)?;
322
323 assert!(
324 matches!(response, tx::Response::Block { .. }),
325 "unexpected response from transaction verifier: {response:?}"
326 );
327
328 sigops += response.sigops();
329
330 if let Some(miner_fee) = response.miner_fee() {
333 block_miner_fees += miner_fee;
334 }
335 }
336
337 if sigops > MAX_BLOCK_SIGOPS {
340 Err(BlockError::TooManyTransparentSignatureOperations {
341 height,
342 hash,
343 sigops,
344 })?;
345 }
346
347 let block_miner_fees =
348 block_miner_fees.map_err(|amount_error| BlockError::SummingMinerFees {
349 height,
350 hash,
351 source: amount_error,
352 })?;
353
354 check::miner_fees_are_valid(
355 &coinbase_tx,
356 height,
357 block_miner_fees,
358 expected_block_subsidy,
359 deferred_pool_balance_change,
360 &network,
361 )?;
362
363 let new_outputs = Arc::into_inner(known_utxos)
365 .expect("all verification tasks using known_utxos are complete");
366
367 let prepared_block = zs::SemanticallyVerifiedBlock {
368 block,
369 hash,
370 height,
371 new_outputs,
372 transaction_hashes,
373 };
374
375 if request.is_proposal() {
377 return match state_service
378 .ready()
379 .await
380 .map_err(VerifyBlockError::ValidateProposal)?
381 .call(zs::Request::CheckBlockProposalValidity(prepared_block))
382 .await
383 .map_err(VerifyBlockError::ValidateProposal)?
384 {
385 zs::Response::ValidBlockProposal => Ok(hash),
386 _ => unreachable!("wrong response for CheckBlockProposalValidity"),
387 };
388 }
389
390 match state_service
391 .ready()
392 .await
393 .map_err(|source| VerifyBlockError::StateService { source, hash })?
394 .call(zs::Request::CommitSemanticallyVerifiedBlock(prepared_block))
395 .await
396 {
397 Ok(zs::Response::Committed(committed_hash)) => {
398 assert_eq!(committed_hash, hash, "state must commit correct hash");
399 Ok(hash)
400 }
401
402 Err(source) => Err(map_commit_error(source, hash)),
403
404 _ => unreachable!("wrong response for CommitSemanticallyVerifiedBlock"),
405 }
406 }
407 .instrument(span)
408 .boxed()
409 }
410}