2020-10-16 16:44:30 -07:00
|
|
|
//! Consensus-based block verification.
|
2020-07-09 23:51:01 -07:00
|
|
|
//!
|
2020-10-16 16:44:30 -07:00
|
|
|
//! In contrast to checkpoint verification, which only checks hardcoded
|
|
|
|
//! hashes, block verification checks all Zcash consensus rules.
|
2020-07-09 23:51:01 -07:00
|
|
|
//!
|
2020-10-16 16:44:30 -07:00
|
|
|
//! The block verifier performs all of the semantic validation checks.
|
|
|
|
//! If accepted, the block is sent to the state service for contextual
|
|
|
|
//! verification, where it may be accepted or rejected.
|
2020-07-09 23:51:01 -07:00
|
|
|
|
|
|
|
use std::{
|
2020-11-20 19:47:30 -08:00
|
|
|
collections::HashMap,
|
2020-07-09 23:51:01 -07:00
|
|
|
future::Future,
|
|
|
|
pin::Pin,
|
|
|
|
sync::Arc,
|
|
|
|
task::{Context, Poll},
|
|
|
|
};
|
2020-09-09 18:53:40 -07:00
|
|
|
|
|
|
|
use chrono::Utc;
|
2020-10-16 15:17:49 -07:00
|
|
|
use futures::stream::FuturesUnordered;
|
2020-09-09 18:53:40 -07:00
|
|
|
use futures_util::FutureExt;
|
2020-09-21 11:54:06 -07:00
|
|
|
use thiserror::Error;
|
2020-09-09 20:16:11 -07:00
|
|
|
use tower::{Service, ServiceExt};
|
2020-11-19 16:55:36 -08:00
|
|
|
use tracing::Instrument;
|
2020-07-09 23:51:01 -07:00
|
|
|
|
2020-09-21 11:54:06 -07:00
|
|
|
use zebra_chain::{
|
|
|
|
block::{self, Block},
|
2020-10-12 13:54:48 -07:00
|
|
|
parameters::Network,
|
2020-11-24 19:55:15 -08:00
|
|
|
transaction, transparent,
|
2020-09-21 11:54:06 -07:00
|
|
|
work::equihash,
|
|
|
|
};
|
2020-09-09 18:53:40 -07:00
|
|
|
use zebra_state as zs;
|
|
|
|
|
2020-11-24 19:55:15 -08:00
|
|
|
use crate::{error::*, transaction as tx};
|
2020-10-16 15:17:49 -07:00
|
|
|
use crate::{script, BoxError};
|
2020-09-09 18:53:40 -07:00
|
|
|
|
|
|
|
mod check;
|
2020-10-12 13:54:48 -07:00
|
|
|
mod subsidy;
|
2020-09-09 18:53:40 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests;
|
2020-07-09 23:51:01 -07:00
|
|
|
|
2020-10-16 16:44:30 -07:00
|
|
|
/// Asynchronous block verification.
|
2020-07-30 17:21:20 -07:00
|
|
|
#[derive(Debug)]
|
2020-10-16 15:17:49 -07:00
|
|
|
pub struct BlockVerifier<S> {
|
2020-10-12 13:54:48 -07:00
|
|
|
/// The network to be verified.
|
|
|
|
network: Network,
|
2020-07-09 23:51:01 -07:00
|
|
|
state_service: S,
|
2020-11-24 19:55:15 -08:00
|
|
|
transaction_verifier: tx::Verifier<S>,
|
2020-07-09 23:51:01 -07:00
|
|
|
}
|
|
|
|
|
2020-10-26 23:42:27 -07:00
|
|
|
// TODO: dedupe with crate::error::BlockError
|
2020-09-21 11:54:06 -07:00
|
|
|
#[non_exhaustive]
|
|
|
|
#[derive(Debug, Error)]
|
|
|
|
pub enum VerifyBlockError {
|
|
|
|
#[error("unable to verify depth for block {hash} from chain state during block verification")]
|
|
|
|
Depth { source: BoxError, hash: block::Hash },
|
2020-10-26 23:42:27 -07:00
|
|
|
|
2020-09-21 11:54:06 -07:00
|
|
|
#[error(transparent)]
|
|
|
|
Block {
|
|
|
|
#[from]
|
|
|
|
source: BlockError,
|
|
|
|
},
|
2020-10-26 23:42:27 -07:00
|
|
|
|
2020-09-21 11:54:06 -07:00
|
|
|
#[error(transparent)]
|
|
|
|
Equihash {
|
|
|
|
#[from]
|
|
|
|
source: equihash::Error,
|
|
|
|
},
|
2020-10-26 23:42:27 -07:00
|
|
|
|
2020-09-21 11:54:06 -07:00
|
|
|
#[error(transparent)]
|
2020-10-12 14:33:32 -07:00
|
|
|
Time(zebra_chain::block::BlockTimeError),
|
2020-10-26 23:42:27 -07:00
|
|
|
|
2020-09-21 11:54:06 -07:00
|
|
|
#[error("unable to commit block after semantic verification")]
|
|
|
|
Commit(#[source] BoxError),
|
2020-10-26 23:42:27 -07:00
|
|
|
|
2020-10-16 15:17:49 -07:00
|
|
|
#[error("invalid transaction")]
|
2020-10-26 23:42:27 -07:00
|
|
|
Transaction(#[source] TransactionError),
|
2020-09-21 11:54:06 -07:00
|
|
|
}
|
|
|
|
|
2020-09-09 20:16:11 -07:00
|
|
|
impl<S> BlockVerifier<S>
|
|
|
|
where
|
|
|
|
S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
|
|
|
|
S::Future: Send + 'static,
|
|
|
|
{
|
2020-10-12 13:54:48 -07:00
|
|
|
pub fn new(network: Network, state_service: S) -> Self {
|
2020-11-24 12:30:58 -08:00
|
|
|
let transaction_verifier =
|
2020-11-24 19:55:15 -08:00
|
|
|
tx::Verifier::new(network, script::Verifier::new(state_service.clone()));
|
2020-10-16 15:17:49 -07:00
|
|
|
|
2020-10-12 13:54:48 -07:00
|
|
|
Self {
|
|
|
|
network,
|
|
|
|
state_service,
|
2020-10-16 15:17:49 -07:00
|
|
|
transaction_verifier,
|
2020-10-12 13:54:48 -07:00
|
|
|
}
|
2020-09-09 20:16:11 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-09 23:51:01 -07:00
|
|
|
impl<S> Service<Arc<Block>> for BlockVerifier<S>
|
|
|
|
where
|
2020-09-09 18:53:40 -07:00
|
|
|
S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
|
2020-07-09 23:51:01 -07:00
|
|
|
S::Future: Send + 'static,
|
|
|
|
{
|
2020-08-15 23:20:01 -07:00
|
|
|
type Response = block::Hash;
|
2020-09-21 11:54:06 -07:00
|
|
|
type Error = VerifyBlockError;
|
2020-07-09 23:51:01 -07:00
|
|
|
type Future =
|
|
|
|
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
|
|
|
|
|
|
|
|
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
2020-07-20 18:31:01 -07:00
|
|
|
// We use the state for contextual verification, and we expect those
|
|
|
|
// queries to be fast. So we don't need to call
|
|
|
|
// `state_service.poll_ready()` here.
|
2020-07-09 23:51:01 -07:00
|
|
|
Poll::Ready(Ok(()))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn call(&mut self, block: Arc<Block>) -> Self::Future {
|
2020-09-02 21:23:57 -07:00
|
|
|
let mut state_service = self.state_service.clone();
|
2020-10-16 15:17:49 -07:00
|
|
|
let mut transaction_verifier = self.transaction_verifier.clone();
|
2020-10-12 13:54:48 -07:00
|
|
|
let network = self.network;
|
2020-07-09 23:51:01 -07:00
|
|
|
|
2020-11-19 16:55:36 -08:00
|
|
|
// We don't include the block hash, because it's likely already in a parent span
|
|
|
|
let span = tracing::debug_span!("block", height = ?block.coinbase_height());
|
|
|
|
|
2020-07-23 20:17:39 -07:00
|
|
|
// TODO(jlusby): Error = Report, handle errors from state_service.
|
2020-07-09 23:51:01 -07:00
|
|
|
async move {
|
2020-11-19 16:55:36 -08:00
|
|
|
let hash = block.hash();
|
2020-09-09 18:53:40 -07:00
|
|
|
// Check that this block is actually a new block.
|
2020-11-20 15:17:36 -08:00
|
|
|
tracing::trace!("checking that block is not already in state");
|
2020-09-21 11:54:06 -07:00
|
|
|
match state_service
|
|
|
|
.ready_and()
|
|
|
|
.await
|
|
|
|
.map_err(|source| VerifyBlockError::Depth { source, hash })?
|
|
|
|
.call(zs::Request::Depth(hash))
|
|
|
|
.await
|
|
|
|
.map_err(|source| VerifyBlockError::Depth { source, hash })?
|
|
|
|
{
|
2020-09-09 18:53:40 -07:00
|
|
|
zs::Response::Depth(Some(depth)) => {
|
2020-09-21 11:54:06 -07:00
|
|
|
return Err(BlockError::AlreadyInChain(hash, depth).into())
|
|
|
|
}
|
|
|
|
zs::Response::Depth(None) => {}
|
2020-09-09 18:53:40 -07:00
|
|
|
_ => unreachable!("wrong response to Request::Depth"),
|
|
|
|
}
|
|
|
|
|
2020-11-20 15:17:36 -08:00
|
|
|
tracing::trace!("performing block checks");
|
2020-08-05 22:26:26 -07:00
|
|
|
let height = block
|
|
|
|
.coinbase_height()
|
2020-09-28 18:10:51 -07:00
|
|
|
.ok_or(BlockError::MissingHeight(hash))?;
|
2020-08-16 11:42:02 -07:00
|
|
|
if height > block::Height::MAX {
|
2020-09-21 11:54:06 -07:00
|
|
|
Err(BlockError::MaxHeight(height, hash, block::Height::MAX))?;
|
2020-08-05 22:26:26 -07:00
|
|
|
}
|
|
|
|
|
2020-08-03 00:37:08 -07:00
|
|
|
// Do the difficulty checks first, to raise the threshold for
|
|
|
|
// attacks that use any other fields.
|
2020-10-12 15:17:40 -07:00
|
|
|
check::difficulty_is_valid(&block.header, network, &height, &hash)?;
|
2020-10-12 13:54:48 -07:00
|
|
|
check::equihash_solution_is_valid(&block.header)?;
|
2020-08-03 00:37:08 -07:00
|
|
|
|
2020-11-30 15:24:29 -08:00
|
|
|
// Next, check the Merkle root validity, to ensure that
|
|
|
|
// the header binds to the transactions in the blocks.
|
2020-07-09 23:51:01 -07:00
|
|
|
|
2020-11-24 19:55:15 -08:00
|
|
|
// Precomputing this avoids duplicating transaction hash computations.
|
|
|
|
let transaction_hashes = block
|
|
|
|
.transactions
|
|
|
|
.iter()
|
|
|
|
.map(|t| t.hash())
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
check::merkle_root_validity(&block, &transaction_hashes)?;
|
2020-08-03 00:37:08 -07:00
|
|
|
|
2020-11-30 15:24:29 -08:00
|
|
|
// Since errors cause an early exit, try to do the
|
|
|
|
// quick checks first.
|
|
|
|
|
|
|
|
// Field validity and structure checks
|
|
|
|
let now = Utc::now();
|
|
|
|
check::time_is_valid_at(&block.header, now, &height, &hash)
|
|
|
|
.map_err(VerifyBlockError::Time)?;
|
|
|
|
check::coinbase_is_first(&block)?;
|
|
|
|
check::subsidy_is_valid(&block, network)?;
|
|
|
|
|
2020-10-16 15:17:49 -07:00
|
|
|
let mut async_checks = FuturesUnordered::new();
|
|
|
|
|
2020-11-24 19:55:15 -08:00
|
|
|
let known_utxos = new_outputs(&block, &transaction_hashes);
|
2020-10-16 15:17:49 -07:00
|
|
|
for transaction in &block.transactions {
|
|
|
|
let rsp = transaction_verifier
|
|
|
|
.ready_and()
|
|
|
|
.await
|
|
|
|
.expect("transaction verifier is always ready")
|
2020-11-24 19:55:15 -08:00
|
|
|
.call(tx::Request::Block {
|
2020-11-20 19:47:30 -08:00
|
|
|
transaction: transaction.clone(),
|
|
|
|
known_utxos: known_utxos.clone(),
|
2020-11-24 12:30:58 -08:00
|
|
|
height,
|
2020-11-20 19:47:30 -08:00
|
|
|
});
|
2020-10-16 15:17:49 -07:00
|
|
|
async_checks.push(rsp);
|
|
|
|
}
|
2020-11-20 15:17:36 -08:00
|
|
|
tracing::trace!(len = async_checks.len(), "built async tx checks");
|
2020-10-16 15:17:49 -07:00
|
|
|
|
|
|
|
use futures::StreamExt;
|
|
|
|
while let Some(result) = async_checks.next().await {
|
2020-11-20 15:17:36 -08:00
|
|
|
tracing::trace!(?result, remaining = async_checks.len());
|
2020-10-16 15:17:49 -07:00
|
|
|
result.map_err(VerifyBlockError::Transaction)?;
|
|
|
|
}
|
2020-10-26 06:11:52 -07:00
|
|
|
|
2020-10-26 05:38:35 -07:00
|
|
|
// Update the metrics after all the validation is finished
|
2020-10-26 05:32:57 -07:00
|
|
|
tracing::trace!("verified block");
|
|
|
|
metrics::gauge!("block.verified.block.height", height.0 as _);
|
|
|
|
metrics::counter!("block.verified.block.count", 1);
|
2020-10-16 15:17:49 -07:00
|
|
|
|
2020-09-09 18:53:40 -07:00
|
|
|
// Finally, submit the block for contextual verification.
|
state: introduce PreparedBlock, FinalizedBlock
This change introduces two new types:
- `PreparedBlock`, representing a block which has undergone semantic
validation and has been prepared for contextual validation;
- `FinalizedBlock`, representing a block which is ready to be finalized
immediately;
and changes the `Request::CommitBlock`,`Request::CommitFinalizedBlock`
variants to use these types instead of their previous fields.
This change solves the problem of passing data between semantic
validation and contextual validation, and cleans up the state code by
allowing it to pass around a bundle of data. Previously, the state code
just passed around an `Arc<Block>`, which forced it to needlessly
recompute block hashes and other data, and was incompatible with the
already-known but not-yet-implemented data transfer requirements, namely
passing in the Sprout and Sapling anchors computed during contextual
validation.
This commit propagates the `PreparedBlock` and `FinalizedBlock` types
through the state code but only uses their data opportunistically, e.g.,
changing .hash() computations to use the precomputed hash. In the
future, these structures can be extended to pass data through the
verification pipeline for reuse as appropriate. For instance, these
changes allow the sprout and sapling anchors to be propagated through
the state.
2020-11-21 01:16:14 -08:00
|
|
|
let new_outputs = Arc::try_unwrap(known_utxos)
|
|
|
|
.expect("all verification tasks using known_utxos are complete");
|
|
|
|
let prepared_block = zs::PreparedBlock {
|
|
|
|
block,
|
|
|
|
hash,
|
|
|
|
height,
|
|
|
|
new_outputs,
|
2020-11-24 19:55:15 -08:00
|
|
|
transaction_hashes,
|
state: introduce PreparedBlock, FinalizedBlock
This change introduces two new types:
- `PreparedBlock`, representing a block which has undergone semantic
validation and has been prepared for contextual validation;
- `FinalizedBlock`, representing a block which is ready to be finalized
immediately;
and changes the `Request::CommitBlock`,`Request::CommitFinalizedBlock`
variants to use these types instead of their previous fields.
This change solves the problem of passing data between semantic
validation and contextual validation, and cleans up the state code by
allowing it to pass around a bundle of data. Previously, the state code
just passed around an `Arc<Block>`, which forced it to needlessly
recompute block hashes and other data, and was incompatible with the
already-known but not-yet-implemented data transfer requirements, namely
passing in the Sprout and Sapling anchors computed during contextual
validation.
This commit propagates the `PreparedBlock` and `FinalizedBlock` types
through the state code but only uses their data opportunistically, e.g.,
changing .hash() computations to use the precomputed hash. In the
future, these structures can be extended to pass data through the
verification pipeline for reuse as appropriate. For instance, these
changes allow the sprout and sapling anchors to be propagated through
the state.
2020-11-21 01:16:14 -08:00
|
|
|
};
|
2020-09-10 10:34:59 -07:00
|
|
|
match state_service
|
|
|
|
.ready_and()
|
2020-09-21 11:54:06 -07:00
|
|
|
.await
|
|
|
|
.map_err(VerifyBlockError::Commit)?
|
state: introduce PreparedBlock, FinalizedBlock
This change introduces two new types:
- `PreparedBlock`, representing a block which has undergone semantic
validation and has been prepared for contextual validation;
- `FinalizedBlock`, representing a block which is ready to be finalized
immediately;
and changes the `Request::CommitBlock`,`Request::CommitFinalizedBlock`
variants to use these types instead of their previous fields.
This change solves the problem of passing data between semantic
validation and contextual validation, and cleans up the state code by
allowing it to pass around a bundle of data. Previously, the state code
just passed around an `Arc<Block>`, which forced it to needlessly
recompute block hashes and other data, and was incompatible with the
already-known but not-yet-implemented data transfer requirements, namely
passing in the Sprout and Sapling anchors computed during contextual
validation.
This commit propagates the `PreparedBlock` and `FinalizedBlock` types
through the state code but only uses their data opportunistically, e.g.,
changing .hash() computations to use the precomputed hash. In the
future, these structures can be extended to pass data through the
verification pipeline for reuse as appropriate. For instance, these
changes allow the sprout and sapling anchors to be propagated through
the state.
2020-11-21 01:16:14 -08:00
|
|
|
.call(zs::Request::CommitBlock(prepared_block))
|
2020-09-21 11:54:06 -07:00
|
|
|
.await
|
|
|
|
.map_err(VerifyBlockError::Commit)?
|
2020-09-10 10:34:59 -07:00
|
|
|
{
|
2020-09-09 18:53:40 -07:00
|
|
|
zs::Response::Committed(committed_hash) => {
|
2020-09-10 10:34:59 -07:00
|
|
|
assert_eq!(committed_hash, hash, "state must commit correct hash");
|
2020-09-02 21:23:57 -07:00
|
|
|
Ok(hash)
|
|
|
|
}
|
2020-09-10 10:34:59 -07:00
|
|
|
_ => unreachable!("wrong response for CommitBlock"),
|
2020-09-02 21:23:57 -07:00
|
|
|
}
|
2020-07-09 23:51:01 -07:00
|
|
|
}
|
2020-11-19 16:55:36 -08:00
|
|
|
.instrument(span)
|
2020-07-09 23:51:01 -07:00
|
|
|
.boxed()
|
|
|
|
}
|
|
|
|
}
|
2020-11-20 19:47:30 -08:00
|
|
|
|
2020-11-24 19:55:15 -08:00
|
|
|
/// Compute an index of newly created transparent outputs, given a block and a
|
|
|
|
/// list of precomputed transaction hashes.
|
|
|
|
fn new_outputs(
|
|
|
|
block: &Block,
|
|
|
|
transaction_hashes: &[transaction::Hash],
|
|
|
|
) -> Arc<HashMap<transparent::OutPoint, zs::Utxo>> {
|
2020-11-23 12:02:57 -08:00
|
|
|
let mut new_outputs = HashMap::default();
|
|
|
|
let height = block.coinbase_height().expect("block has coinbase height");
|
2020-11-24 19:55:15 -08:00
|
|
|
for (transaction, hash) in block
|
|
|
|
.transactions
|
|
|
|
.iter()
|
|
|
|
.zip(transaction_hashes.iter().cloned())
|
|
|
|
{
|
2020-11-23 12:02:57 -08:00
|
|
|
let from_coinbase = transaction.is_coinbase();
|
2020-11-20 19:47:30 -08:00
|
|
|
for (index, output) in transaction.outputs().iter().cloned().enumerate() {
|
|
|
|
let index = index as u32;
|
2020-11-23 12:02:57 -08:00
|
|
|
new_outputs.insert(
|
|
|
|
transparent::OutPoint { hash, index },
|
|
|
|
zs::Utxo {
|
|
|
|
output,
|
|
|
|
height,
|
|
|
|
from_coinbase,
|
|
|
|
},
|
|
|
|
);
|
2020-11-20 19:47:30 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-23 12:02:57 -08:00
|
|
|
Arc::new(new_outputs)
|
2020-11-20 19:47:30 -08:00
|
|
|
}
|