1use 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#[derive(Debug)]
46pub struct SemanticBlockVerifier<S, V> {
47 network: Network,
49 state_service: S,
50 transaction_verifier: V,
51}
52
53#[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("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 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 #[error("state service error for block {hash}: {source}")]
94 StateService { source: BoxError, hash: block::Hash },
95}
96
97impl VerifyBlockError {
98 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 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
121fn 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
140pub 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 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 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 let span = tracing::debug_span!("block", height = ?block.coinbase_height());
212
213 async move {
214 let hash = block.hash();
215 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 if height > block::Height::MAX {
240 Err(BlockError::MaxHeight(height, hash, block::Height::MAX))?;
241 }
242
243 if request.is_proposal() || network.disable_pow() {
247 check::difficulty_threshold_is_valid(&block.header, &network, &height, &hash)?;
248 } else {
249 check::difficulty_is_valid(&block.header, &network, &height, &hash)?;
252 check::equihash_solution_is_valid(&block.header)?;
253 }
254
255 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 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 let deferred_pool_balance_change =
278 check::subsidy_is_valid(&block, &network, expected_block_subsidy)?;
279
280 tx::check::coinbase_outputs_are_decryptable(&coinbase_tx, &network, height)?;
284
285 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 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 if let Some(miner_fee) = response.miner_fee {
329 block_miner_fees += miner_fee;
330 }
331 }
332
333 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 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 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}