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}")]
79 Commit(#[source] BoxError),
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
93impl VerifyBlockError {
94 pub fn is_duplicate_request(&self) -> bool {
97 match self {
98 VerifyBlockError::Block { source, .. } => source.is_duplicate_request(),
99 _ => false,
100 }
101 }
102
103 pub fn misbehavior_score(&self) -> u32 {
105 use VerifyBlockError::*;
107 match self {
108 Block { source } => source.misbehavior_score(),
109 Equihash { .. } => 100,
110 _other => 0,
111 }
112 }
113}
114
115pub const MAX_BLOCK_SIGOPS: u32 = 20_000;
123
124impl<S, V> SemanticBlockVerifier<S, V>
125where
126 S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
127 S::Future: Send + 'static,
128 V: Service<tx::Request, Response = tx::Response, Error = BoxError> + Send + Clone + 'static,
129 V::Future: Send + 'static,
130{
131 pub fn new(network: &Network, state_service: S, transaction_verifier: V) -> Self {
133 Self {
134 network: network.clone(),
135 state_service,
136 transaction_verifier,
137 }
138 }
139}
140
141impl<S, V> Service<Request> for SemanticBlockVerifier<S, V>
142where
143 S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
144 S::Future: Send + 'static,
145 V: Service<tx::Request, Response = tx::Response, Error = BoxError> + Send + Clone + 'static,
146 V::Future: Send + 'static,
147{
148 type Response = block::Hash;
149 type Error = VerifyBlockError;
150 type Future =
151 Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
152
153 fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
154 Poll::Ready(Ok(()))
158 }
159
160 fn call(&mut self, request: Request) -> Self::Future {
161 let mut state_service = self.state_service.clone();
162 let mut transaction_verifier = self.transaction_verifier.clone();
163 let network = self.network.clone();
164
165 let block = request.block();
166
167 let span = tracing::debug_span!("block", height = ?block.coinbase_height());
169
170 async move {
171 let hash = block.hash();
172 tracing::trace!("checking that block is not already in state");
174 match state_service
175 .ready()
176 .await
177 .map_err(|source| VerifyBlockError::Depth { source, hash })?
178 .call(zs::Request::KnownBlock(hash))
179 .await
180 .map_err(|source| VerifyBlockError::Depth { source, hash })?
181 {
182 zs::Response::KnownBlock(Some(location)) => {
183 return Err(BlockError::AlreadyInChain(hash, location).into())
184 }
185 zs::Response::KnownBlock(None) => {}
186 _ => unreachable!("wrong response to Request::KnownBlock"),
187 }
188
189 tracing::trace!("performing block checks");
190 let height = block
191 .coinbase_height()
192 .ok_or(BlockError::MissingHeight(hash))?;
193
194 if height > block::Height::MAX {
197 Err(BlockError::MaxHeight(height, hash, block::Height::MAX))?;
198 }
199
200 if request.is_proposal() || network.disable_pow() {
204 check::difficulty_threshold_is_valid(&block.header, &network, &height, &hash)?;
205 } else {
206 check::difficulty_is_valid(&block.header, &network, &height, &hash)?;
209 check::equihash_solution_is_valid(&block.header)?;
210 }
211
212 let transaction_hashes: Arc<[_]> =
217 block.transactions.iter().map(|t| t.hash()).collect();
218
219 check::merkle_root_validity(&network, &block, &transaction_hashes)?;
220
221 let now = Utc::now();
226 check::time_is_valid_at(&block.header, now, &height, &hash)
227 .map_err(VerifyBlockError::Time)?;
228 let coinbase_tx = check::coinbase_is_first(&block)?;
229
230 let expected_block_subsidy =
231 zebra_chain::parameters::subsidy::block_subsidy(height, &network)?;
232
233 let deferred_pool_balance_change =
235 check::subsidy_is_valid(&block, &network, expected_block_subsidy)?;
236
237 tx::check::coinbase_outputs_are_decryptable(&coinbase_tx, &network, height)?;
241
242 let mut async_checks = FuturesUnordered::new();
244
245 let known_utxos = Arc::new(transparent::new_ordered_outputs(
246 &block,
247 &transaction_hashes,
248 ));
249
250 let known_outpoint_hashes: Arc<HashSet<transaction::Hash>> =
251 Arc::new(known_utxos.keys().map(|outpoint| outpoint.hash).collect());
252
253 for (&transaction_hash, transaction) in
254 transaction_hashes.iter().zip(block.transactions.iter())
255 {
256 let rsp = transaction_verifier
257 .ready()
258 .await
259 .expect("transaction verifier is always ready")
260 .call(tx::Request::Block {
261 transaction_hash,
262 transaction: transaction.clone(),
263 known_outpoint_hashes: known_outpoint_hashes.clone(),
264 known_utxos: known_utxos.clone(),
265 height,
266 time: block.header.time,
267 });
268 async_checks.push(rsp);
269 }
270 tracing::trace!(len = async_checks.len(), "built async tx checks");
271
272 let mut sigops = 0;
276 let mut block_miner_fees = Ok(Amount::zero());
277
278 use futures::StreamExt;
279 while let Some(result) = async_checks.next().await {
280 tracing::trace!(?result, remaining = async_checks.len());
281 let response = result
282 .map_err(Into::into)
283 .map_err(VerifyBlockError::Transaction)?;
284
285 assert!(
286 matches!(response, tx::Response::Block { .. }),
287 "unexpected response from transaction verifier: {response:?}"
288 );
289
290 sigops += response.sigops();
291
292 if let Some(miner_fee) = response.miner_fee() {
295 block_miner_fees += miner_fee;
296 }
297 }
298
299 if sigops > MAX_BLOCK_SIGOPS {
302 Err(BlockError::TooManyTransparentSignatureOperations {
303 height,
304 hash,
305 sigops,
306 })?;
307 }
308
309 let block_miner_fees =
310 block_miner_fees.map_err(|amount_error| BlockError::SummingMinerFees {
311 height,
312 hash,
313 source: amount_error,
314 })?;
315
316 check::miner_fees_are_valid(
317 &coinbase_tx,
318 height,
319 block_miner_fees,
320 expected_block_subsidy,
321 deferred_pool_balance_change,
322 &network,
323 )?;
324
325 let new_outputs = Arc::into_inner(known_utxos)
327 .expect("all verification tasks using known_utxos are complete");
328
329 let prepared_block = zs::SemanticallyVerifiedBlock {
330 block,
331 hash,
332 height,
333 new_outputs,
334 transaction_hashes,
335 deferred_pool_balance_change: Some(deferred_pool_balance_change),
336 };
337
338 if request.is_proposal() {
340 return match state_service
341 .ready()
342 .await
343 .map_err(VerifyBlockError::ValidateProposal)?
344 .call(zs::Request::CheckBlockProposalValidity(prepared_block))
345 .await
346 .map_err(VerifyBlockError::ValidateProposal)?
347 {
348 zs::Response::ValidBlockProposal => Ok(hash),
349 _ => unreachable!("wrong response for CheckBlockProposalValidity"),
350 };
351 }
352
353 match state_service
354 .ready()
355 .await
356 .map_err(VerifyBlockError::Commit)?
357 .call(zs::Request::CommitSemanticallyVerifiedBlock(prepared_block))
358 .await
359 .map_err(VerifyBlockError::Commit)?
360 {
361 zs::Response::Committed(committed_hash) => {
362 assert_eq!(committed_hash, hash, "state must commit correct hash");
363 Ok(hash)
364 }
365 _ => unreachable!("wrong response for CommitSemanticallyVerifiedBlock"),
366 }
367 }
368 .instrument(span)
369 .boxed()
370 }
371}