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::{
29 subsidy::{FundingStreamReceiver, SubsidyError},
30 Network,
31 },
32 transaction, transparent,
33 work::equihash,
34};
35use zebra_state as zs;
36
37use crate::{error::*, transaction as tx, BoxError};
38
39pub mod check;
40pub mod request;
41pub mod subsidy;
42
43pub use request::Request;
44
45#[cfg(test)]
46mod tests;
47
48#[derive(Debug)]
50pub struct SemanticBlockVerifier<S, V> {
51 network: Network,
53 state_service: S,
54 transaction_verifier: V,
55}
56
57#[non_exhaustive]
60#[allow(missing_docs)]
61#[derive(Debug, Error)]
62pub enum VerifyBlockError {
63 #[error("unable to verify depth for block {hash} from chain state during block verification")]
64 Depth { source: BoxError, hash: block::Hash },
65
66 #[error(transparent)]
67 Block {
68 #[from]
69 source: BlockError,
70 },
71
72 #[error(transparent)]
73 Equihash {
74 #[from]
75 source: equihash::Error,
76 },
77
78 #[error(transparent)]
79 Time(zebra_chain::block::BlockTimeError),
80
81 #[error("unable to commit block after semantic verification: {0}")]
82 Commit(#[source] BoxError),
84
85 #[error("unable to validate block proposal: failed semantic verification (proof of work is not checked for proposals): {0}")]
86 ValidateProposal(#[source] BoxError),
88
89 #[error("invalid transaction: {0}")]
90 Transaction(#[from] TransactionError),
91
92 #[error("invalid block subsidy: {0}")]
93 Subsidy(#[from] SubsidyError),
94}
95
96impl VerifyBlockError {
97 pub fn is_duplicate_request(&self) -> bool {
100 match self {
101 VerifyBlockError::Block { source, .. } => source.is_duplicate_request(),
102 _ => false,
103 }
104 }
105
106 pub fn misbehavior_score(&self) -> u32 {
108 use VerifyBlockError::*;
110 match self {
111 Block { source } => source.misbehavior_score(),
112 Equihash { .. } => 100,
113 _other => 0,
114 }
115 }
116}
117
118pub const MAX_BLOCK_SIGOPS: u64 = 20_000;
126
127impl<S, V> SemanticBlockVerifier<S, V>
128where
129 S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
130 S::Future: Send + 'static,
131 V: Service<tx::Request, Response = tx::Response, Error = BoxError> + Send + Clone + 'static,
132 V::Future: Send + 'static,
133{
134 pub fn new(network: &Network, state_service: S, transaction_verifier: V) -> Self {
136 Self {
137 network: network.clone(),
138 state_service,
139 transaction_verifier,
140 }
141 }
142}
143
144impl<S, V> Service<Request> for SemanticBlockVerifier<S, V>
145where
146 S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
147 S::Future: Send + 'static,
148 V: Service<tx::Request, Response = tx::Response, Error = BoxError> + Send + Clone + 'static,
149 V::Future: Send + 'static,
150{
151 type Response = block::Hash;
152 type Error = VerifyBlockError;
153 type Future =
154 Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
155
156 fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
157 Poll::Ready(Ok(()))
161 }
162
163 fn call(&mut self, request: Request) -> Self::Future {
164 let mut state_service = self.state_service.clone();
165 let mut transaction_verifier = self.transaction_verifier.clone();
166 let network = self.network.clone();
167
168 let block = request.block();
169
170 let span = tracing::debug_span!("block", height = ?block.coinbase_height());
172
173 async move {
174 let hash = block.hash();
175 tracing::trace!("checking that block is not already in state");
177 match state_service
178 .ready()
179 .await
180 .map_err(|source| VerifyBlockError::Depth { source, hash })?
181 .call(zs::Request::KnownBlock(hash))
182 .await
183 .map_err(|source| VerifyBlockError::Depth { source, hash })?
184 {
185 zs::Response::KnownBlock(Some(location)) => {
186 return Err(BlockError::AlreadyInChain(hash, location).into())
187 }
188 zs::Response::KnownBlock(None) => {}
189 _ => unreachable!("wrong response to Request::KnownBlock"),
190 }
191
192 tracing::trace!("performing block checks");
193 let height = block
194 .coinbase_height()
195 .ok_or(BlockError::MissingHeight(hash))?;
196
197 if height > block::Height::MAX {
200 Err(BlockError::MaxHeight(height, hash, block::Height::MAX))?;
201 }
202
203 if request.is_proposal() || network.disable_pow() {
207 check::difficulty_threshold_is_valid(&block.header, &network, &height, &hash)?;
208 } else {
209 check::difficulty_is_valid(&block.header, &network, &height, &hash)?;
212 check::equihash_solution_is_valid(&block.header)?;
213 }
214
215 let transaction_hashes: Arc<[_]> =
220 block.transactions.iter().map(|t| t.hash()).collect();
221
222 check::merkle_root_validity(&network, &block, &transaction_hashes)?;
223
224 let now = Utc::now();
229 check::time_is_valid_at(&block.header, now, &height, &hash)
230 .map_err(VerifyBlockError::Time)?;
231 let coinbase_tx = check::coinbase_is_first(&block)?;
232
233 let expected_block_subsidy =
234 zebra_chain::parameters::subsidy::block_subsidy(height, &network)?;
235
236 check::subsidy_is_valid(&block, &network, expected_block_subsidy)?;
237
238 tx::check::coinbase_outputs_are_decryptable(&coinbase_tx, &network, height)?;
242
243 let mut async_checks = FuturesUnordered::new();
245
246 let known_utxos = Arc::new(transparent::new_ordered_outputs(
247 &block,
248 &transaction_hashes,
249 ));
250
251 let known_outpoint_hashes: Arc<HashSet<transaction::Hash>> =
252 Arc::new(known_utxos.keys().map(|outpoint| outpoint.hash).collect());
253
254 for (&transaction_hash, transaction) in
255 transaction_hashes.iter().zip(block.transactions.iter())
256 {
257 let rsp = transaction_verifier
258 .ready()
259 .await
260 .expect("transaction verifier is always ready")
261 .call(tx::Request::Block {
262 transaction_hash,
263 transaction: transaction.clone(),
264 known_outpoint_hashes: known_outpoint_hashes.clone(),
265 known_utxos: known_utxos.clone(),
266 height,
267 time: block.header.time,
268 });
269 async_checks.push(rsp);
270 }
271 tracing::trace!(len = async_checks.len(), "built async tx checks");
272
273 let mut legacy_sigop_count = 0;
277 let mut block_miner_fees = Ok(Amount::zero());
278
279 use futures::StreamExt;
280 while let Some(result) = async_checks.next().await {
281 tracing::trace!(?result, remaining = async_checks.len());
282 let response = result
283 .map_err(Into::into)
284 .map_err(VerifyBlockError::Transaction)?;
285
286 assert!(
287 matches!(response, tx::Response::Block { .. }),
288 "unexpected response from transaction verifier: {response:?}"
289 );
290
291 legacy_sigop_count += response.legacy_sigop_count();
292
293 if let Some(miner_fee) = response.miner_fee() {
296 block_miner_fees += miner_fee;
297 }
298 }
299
300 if legacy_sigop_count > MAX_BLOCK_SIGOPS {
303 Err(BlockError::TooManyTransparentSignatureOperations {
304 height,
305 hash,
306 legacy_sigop_count,
307 })?;
308 }
309
310 let expected_deferred_amount = zebra_chain::parameters::subsidy::funding_stream_values(
312 height,
313 &network,
314 expected_block_subsidy,
315 )
316 .expect("we always expect a funding stream hashmap response even if empty")
317 .remove(&FundingStreamReceiver::Deferred)
318 .unwrap_or_default();
319
320 let block_miner_fees =
321 block_miner_fees.map_err(|amount_error| BlockError::SummingMinerFees {
322 height,
323 hash,
324 source: amount_error,
325 })?;
326
327 check::miner_fees_are_valid(
328 &coinbase_tx,
329 height,
330 block_miner_fees,
331 expected_block_subsidy,
332 expected_deferred_amount,
333 &network,
334 )?;
335
336 let new_outputs = Arc::into_inner(known_utxos)
338 .expect("all verification tasks using known_utxos are complete");
339
340 let prepared_block = zs::SemanticallyVerifiedBlock {
341 block,
342 hash,
343 height,
344 new_outputs,
345 transaction_hashes,
346 deferred_balance: Some(expected_deferred_amount),
347 };
348
349 if request.is_proposal() {
351 return match state_service
352 .ready()
353 .await
354 .map_err(VerifyBlockError::ValidateProposal)?
355 .call(zs::Request::CheckBlockProposalValidity(prepared_block))
356 .await
357 .map_err(VerifyBlockError::ValidateProposal)?
358 {
359 zs::Response::ValidBlockProposal => Ok(hash),
360 _ => unreachable!("wrong response for CheckBlockProposalValidity"),
361 };
362 }
363
364 match state_service
365 .ready()
366 .await
367 .map_err(VerifyBlockError::Commit)?
368 .call(zs::Request::CommitSemanticallyVerifiedBlock(prepared_block))
369 .await
370 .map_err(VerifyBlockError::Commit)?
371 {
372 zs::Response::Committed(committed_hash) => {
373 assert_eq!(committed_hash, hash, "state must commit correct hash");
374 Ok(hash)
375 }
376 _ => unreachable!("wrong response for CommitSemanticallyVerifiedBlock"),
377 }
378 }
379 .instrument(span)
380 .boxed()
381 }
382}