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
122pub 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 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 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 let span = tracing::debug_span!("block", height = ?block.coinbase_height());
188
189 async move {
190 let hash = block.hash();
191 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 if height > block::Height::MAX {
216 Err(BlockError::MaxHeight(height, hash, block::Height::MAX))?;
217 }
218
219 if request.is_proposal() || network.disable_pow() {
223 check::difficulty_threshold_is_valid(&block.header, &network, &height, &hash)?;
224 } else {
225 check::difficulty_is_valid(&block.header, &network, &height, &hash)?;
228 check::equihash_solution_is_valid(&block.header)?;
229 }
230
231 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 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 let deferred_pool_balance_change =
254 check::subsidy_is_valid(&block, &network, expected_block_subsidy)?;
255
256 tx::check::coinbase_outputs_are_decryptable(&coinbase_tx, &network, height)?;
260
261 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 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 if let Some(miner_fee) = response.miner_fee() {
314 block_miner_fees += miner_fee;
315 }
316 }
317
318 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 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 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}