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::*;
113 match self {
114 Block { source } => source.misbehavior_score(),
115 Equihash { .. } => 100,
116 _other => 0,
117 }
118 }
119}
120
121pub const MAX_BLOCK_SIGOPS: u32 = 20_000;
129
130impl<S, V> SemanticBlockVerifier<S, V>
131where
132 S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
133 S::Future: Send + 'static,
134 V: Service<tx::Request, Response = tx::Response, Error = BoxError> + Send + Clone + 'static,
135 V::Future: Send + 'static,
136{
137 pub fn new(network: &Network, state_service: S, transaction_verifier: V) -> Self {
139 Self {
140 network: network.clone(),
141 state_service,
142 transaction_verifier,
143 }
144 }
145}
146
147impl<S, V> Service<Request> for SemanticBlockVerifier<S, V>
148where
149 S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
150 S::Future: Send + 'static,
151 V: Service<tx::Request, Response = tx::Response, Error = BoxError> + Send + Clone + 'static,
152 V::Future: Send + 'static,
153{
154 type Response = block::Hash;
155 type Error = VerifyBlockError;
156 type Future =
157 Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
158
159 fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
160 Poll::Ready(Ok(()))
164 }
165
166 fn call(&mut self, request: Request) -> Self::Future {
167 let mut state_service = self.state_service.clone();
168 let mut transaction_verifier = self.transaction_verifier.clone();
169 let network = self.network.clone();
170
171 let block = request.block();
172
173 let span = tracing::debug_span!("block", height = ?block.coinbase_height());
175
176 async move {
177 let hash = block.hash();
178 tracing::trace!("checking that block is not already in state");
180 match state_service
181 .ready()
182 .await
183 .map_err(|source| VerifyBlockError::Depth { source, hash })?
184 .call(zs::Request::KnownBlock(hash))
185 .await
186 .map_err(|source| VerifyBlockError::Depth { source, hash })?
187 {
188 zs::Response::KnownBlock(Some(location)) => {
189 return Err(BlockError::AlreadyInChain(hash, location).into())
190 }
191 zs::Response::KnownBlock(None) => {}
192 _ => unreachable!("wrong response to Request::KnownBlock"),
193 }
194
195 tracing::trace!("performing block checks");
196 let height = block
197 .coinbase_height()
198 .ok_or(BlockError::MissingHeight(hash))?;
199
200 if height > block::Height::MAX {
203 Err(BlockError::MaxHeight(height, hash, block::Height::MAX))?;
204 }
205
206 if request.is_proposal() || network.disable_pow() {
210 check::difficulty_threshold_is_valid(&block.header, &network, &height, &hash)?;
211 } else {
212 check::difficulty_is_valid(&block.header, &network, &height, &hash)?;
215 check::equihash_solution_is_valid(&block.header)?;
216 }
217
218 let transaction_hashes: Arc<[_]> =
223 block.transactions.iter().map(|t| t.hash()).collect();
224
225 check::merkle_root_validity(&network, &block, &transaction_hashes)?;
226
227 let now = Utc::now();
232 check::time_is_valid_at(&block.header, now, &height, &hash)
233 .map_err(VerifyBlockError::Time)?;
234 let coinbase_tx = check::coinbase_is_first(&block)?;
235
236 let expected_block_subsidy =
237 zebra_chain::parameters::subsidy::block_subsidy(height, &network)?;
238
239 let deferred_pool_balance_change =
241 check::subsidy_is_valid(&block, &network, expected_block_subsidy)?;
242
243 tx::check::coinbase_outputs_are_decryptable(&coinbase_tx, &network, height)?;
247
248 let mut async_checks = FuturesUnordered::new();
250
251 let known_utxos = Arc::new(transparent::new_ordered_outputs(
252 &block,
253 &transaction_hashes,
254 ));
255
256 let known_outpoint_hashes: Arc<HashSet<transaction::Hash>> =
257 Arc::new(known_utxos.keys().map(|outpoint| outpoint.hash).collect());
258
259 for (&transaction_hash, transaction) in
260 transaction_hashes.iter().zip(block.transactions.iter())
261 {
262 let rsp = transaction_verifier
263 .ready()
264 .await
265 .expect("transaction verifier is always ready")
266 .call(tx::Request::Block {
267 transaction_hash,
268 transaction: transaction.clone(),
269 known_outpoint_hashes: known_outpoint_hashes.clone(),
270 known_utxos: known_utxos.clone(),
271 height,
272 time: block.header.time,
273 });
274 async_checks.push(rsp);
275 }
276 tracing::trace!(len = async_checks.len(), "built async tx checks");
277
278 let mut sigops = 0;
282 let mut block_miner_fees = Ok(Amount::zero());
283
284 use futures::StreamExt;
285 while let Some(result) = async_checks.next().await {
286 tracing::trace!(?result, remaining = async_checks.len());
287 let response = result
288 .map_err(Into::into)
289 .map_err(VerifyBlockError::Transaction)?;
290
291 assert!(
292 matches!(response, tx::Response::Block { .. }),
293 "unexpected response from transaction verifier: {response:?}"
294 );
295
296 sigops += response.sigops();
297
298 if let Some(miner_fee) = response.miner_fee() {
301 block_miner_fees += miner_fee;
302 }
303 }
304
305 if sigops > MAX_BLOCK_SIGOPS {
308 Err(BlockError::TooManyTransparentSignatureOperations {
309 height,
310 hash,
311 sigops,
312 })?;
313 }
314
315 let block_miner_fees =
316 block_miner_fees.map_err(|amount_error| BlockError::SummingMinerFees {
317 height,
318 hash,
319 source: amount_error,
320 })?;
321
322 check::miner_fees_are_valid(
323 &coinbase_tx,
324 height,
325 block_miner_fees,
326 expected_block_subsidy,
327 deferred_pool_balance_change,
328 &network,
329 )?;
330
331 let new_outputs = Arc::into_inner(known_utxos)
333 .expect("all verification tasks using known_utxos are complete");
334
335 let prepared_block = zs::SemanticallyVerifiedBlock {
336 block,
337 hash,
338 height,
339 new_outputs,
340 transaction_hashes,
341 deferred_pool_balance_change: Some(deferred_pool_balance_change),
342 };
343
344 if request.is_proposal() {
346 return match state_service
347 .ready()
348 .await
349 .map_err(VerifyBlockError::ValidateProposal)?
350 .call(zs::Request::CheckBlockProposalValidity(prepared_block))
351 .await
352 .map_err(VerifyBlockError::ValidateProposal)?
353 {
354 zs::Response::ValidBlockProposal => Ok(hash),
355 _ => unreachable!("wrong response for CheckBlockProposalValidity"),
356 };
357 }
358
359 match state_service
360 .ready()
361 .await
362 .map_err(|source| VerifyBlockError::StateService { source, hash })?
363 .call(zs::Request::CommitSemanticallyVerifiedBlock(prepared_block))
364 .await
365 {
366 Ok(zs::Response::Committed(committed_hash)) => {
367 assert_eq!(committed_hash, hash, "state must commit correct hash");
368 Ok(hash)
369 }
370
371 Err(source) => {
372 if let Some(commit_err) = source.downcast_ref::<zs::CommitBlockError>() {
373 return Err(VerifyBlockError::Commit(commit_err.clone()));
374 }
375
376 Err(VerifyBlockError::StateService { source, hash })
377 }
378
379 _ => unreachable!("wrong response for CommitSemanticallyVerifiedBlock"),
380 }
381 }
382 .instrument(span)
383 .boxed()
384 }
385}