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::{
|
|
|
|
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::{
|
2021-11-23 07:31:56 -08:00
|
|
|
amount::Amount,
|
2020-09-21 11:54:06 -07:00
|
|
|
block::{self, Block},
|
2020-10-12 13:54:48 -07:00
|
|
|
parameters::Network,
|
2021-07-11 19:49:33 -07:00
|
|
|
transparent,
|
2020-09-21 11:54:06 -07:00
|
|
|
work::equihash,
|
|
|
|
};
|
2020-09-09 18:53:40 -07:00
|
|
|
use zebra_state as zs;
|
|
|
|
|
2021-08-25 08:07:26 -07:00
|
|
|
use crate::{error::*, transaction as tx, BoxError};
|
2020-09-09 18:53:40 -07:00
|
|
|
|
2021-03-10 18:10:47 -08:00
|
|
|
pub mod check;
|
2020-10-12 13:54:48 -07:00
|
|
|
mod subsidy;
|
2021-08-25 08:07:26 -07:00
|
|
|
|
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)]
|
2021-08-25 08:07:26 -07:00
|
|
|
pub struct BlockVerifier<S, V> {
|
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,
|
2021-08-25 08:07:26 -07:00
|
|
|
transaction_verifier: V,
|
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]
|
2021-10-19 18:07:19 -07:00
|
|
|
#[allow(missing_docs)]
|
2020-09-21 11:54:06 -07:00
|
|
|
#[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")]
|
2021-08-25 08:07:26 -07:00
|
|
|
Transaction(#[from] TransactionError),
|
2020-09-21 11:54:06 -07:00
|
|
|
}
|
|
|
|
|
2021-11-15 12:55:32 -08:00
|
|
|
/// The maximum allowed number of legacy signature check operations in a block.
|
|
|
|
///
|
|
|
|
/// This consensus rule is not documented, so Zebra follows the `zcashd` implementation.
|
|
|
|
/// We re-use some `zcashd` C++ script code via `zebra-script` and `zcash_script`.
|
|
|
|
///
|
|
|
|
/// See:
|
|
|
|
/// https://github.com/zcash/zcash/blob/bad7f7eadbbb3466bebe3354266c7f69f607fcfd/src/consensus/consensus.h#L30
|
|
|
|
pub const MAX_BLOCK_SIGOPS: u64 = 20_000;
|
|
|
|
|
2021-08-25 08:07:26 -07:00
|
|
|
impl<S, V> BlockVerifier<S, V>
|
2020-09-09 20:16:11 -07:00
|
|
|
where
|
|
|
|
S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
|
|
|
|
S::Future: Send + 'static,
|
2021-08-25 08:07:26 -07:00
|
|
|
V: Service<tx::Request, Response = tx::Response, Error = BoxError> + Send + Clone + 'static,
|
|
|
|
V::Future: Send + 'static,
|
2020-09-09 20:16:11 -07:00
|
|
|
{
|
2021-08-25 08:07:26 -07:00
|
|
|
pub fn new(network: Network, state_service: S, transaction_verifier: V) -> Self {
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-25 08:07:26 -07:00
|
|
|
impl<S, V> Service<Arc<Block>> for BlockVerifier<S, V>
|
2020-07-09 23:51:01 -07:00
|
|
|
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,
|
2021-08-25 08:07:26 -07:00
|
|
|
V: Service<tx::Request, Response = tx::Response, Error = BoxError> + Send + Clone + 'static,
|
|
|
|
V::Future: Send + 'static,
|
2020-07-09 23:51:01 -07:00
|
|
|
{
|
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-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
|
2021-11-02 11:46:57 -07:00
|
|
|
.ready()
|
2020-09-21 11:54:06 -07:00
|
|
|
.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))?;
|
2021-11-15 12:55:32 -08:00
|
|
|
|
2022-02-10 16:32:57 -08:00
|
|
|
// Zebra does not support heights greater than
|
|
|
|
// [`block::Height::MAX`].
|
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.
|
2021-08-30 11:42:07 -07:00
|
|
|
let transaction_hashes: Arc<[_]> =
|
|
|
|
block.transactions.iter().map(|t| t.hash()).collect();
|
2020-11-24 19:55:15 -08:00
|
|
|
|
2021-05-09 18:31:45 -07:00
|
|
|
check::merkle_root_validity(network, &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.
|
|
|
|
|
2021-11-23 07:31:56 -08:00
|
|
|
// Quick field validity and structure checks
|
2020-11-30 15:24:29 -08:00
|
|
|
let now = Utc::now();
|
|
|
|
check::time_is_valid_at(&block.header, now, &height, &hash)
|
|
|
|
.map_err(VerifyBlockError::Time)?;
|
|
|
|
check::coinbase_is_first(&block)?;
|
2021-11-11 14:18:37 -08:00
|
|
|
let coinbase_tx = block
|
|
|
|
.transactions
|
|
|
|
.get(0)
|
|
|
|
.expect("must have coinbase transaction");
|
2020-11-30 15:24:29 -08:00
|
|
|
check::subsidy_is_valid(&block, network)?;
|
|
|
|
|
2021-11-23 07:31:56 -08:00
|
|
|
// Now do the slower checks
|
|
|
|
|
|
|
|
// Check compatibility with ZIP-212 shielded Sapling and Orchard coinbase output decryption
|
|
|
|
tx::check::coinbase_outputs_are_decryptable(coinbase_tx, network, height)?;
|
|
|
|
|
|
|
|
// Send transactions to the transaction verifier to be checked
|
2020-10-16 15:17:49 -07:00
|
|
|
let mut async_checks = FuturesUnordered::new();
|
|
|
|
|
2021-07-19 06:52:32 -07:00
|
|
|
let known_utxos = Arc::new(transparent::new_ordered_outputs(
|
|
|
|
&block,
|
|
|
|
&transaction_hashes,
|
|
|
|
));
|
2020-10-16 15:17:49 -07:00
|
|
|
for transaction in &block.transactions {
|
|
|
|
let rsp = transaction_verifier
|
2021-11-02 11:46:57 -07:00
|
|
|
.ready()
|
2020-10-16 15:17:49 -07:00
|
|
|
.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,
|
2021-11-22 21:53:53 -08:00
|
|
|
time: block.header.time,
|
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
|
|
|
|
2021-11-23 07:31:56 -08:00
|
|
|
// Get the transaction results back from the transaction verifier.
|
|
|
|
|
|
|
|
// Sum up some block totals from the transaction responses.
|
2021-11-15 12:55:32 -08:00
|
|
|
let mut legacy_sigop_count = 0;
|
2021-11-23 07:31:56 -08:00
|
|
|
let mut block_miner_fees = Ok(Amount::zero());
|
2021-11-15 12:55:32 -08:00
|
|
|
|
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());
|
2021-11-15 12:55:32 -08:00
|
|
|
let response = result
|
2021-08-25 08:07:26 -07:00
|
|
|
.map_err(Into::into)
|
|
|
|
.map_err(VerifyBlockError::Transaction)?;
|
2021-11-23 07:31:56 -08:00
|
|
|
|
|
|
|
assert!(
|
|
|
|
matches!(response, tx::Response::Block { .. }),
|
|
|
|
"unexpected response from transaction verifier: {:?}",
|
|
|
|
response
|
|
|
|
);
|
|
|
|
|
2021-11-15 12:55:32 -08:00
|
|
|
legacy_sigop_count += response
|
|
|
|
.legacy_sigop_count()
|
|
|
|
.expect("block transaction responses must have a legacy sigop count");
|
2021-11-23 07:31:56 -08:00
|
|
|
|
|
|
|
// Coinbase transactions consume the miner fee,
|
|
|
|
// so they don't add any value to the block's total miner fee.
|
|
|
|
if let Some(miner_fee) = response.miner_fee() {
|
|
|
|
block_miner_fees += miner_fee;
|
|
|
|
}
|
2021-11-15 12:55:32 -08:00
|
|
|
}
|
|
|
|
|
2021-11-23 07:31:56 -08:00
|
|
|
// Check the summed block totals
|
|
|
|
|
2021-11-15 12:55:32 -08:00
|
|
|
if legacy_sigop_count > MAX_BLOCK_SIGOPS {
|
|
|
|
Err(BlockError::TooManyTransparentSignatureOperations {
|
|
|
|
height,
|
|
|
|
hash,
|
|
|
|
legacy_sigop_count,
|
|
|
|
})?;
|
2020-10-16 15:17:49 -07:00
|
|
|
}
|
2020-10-26 06:11:52 -07:00
|
|
|
|
2021-11-23 19:36:17 -08:00
|
|
|
let block_miner_fees =
|
2021-11-23 07:31:56 -08:00
|
|
|
block_miner_fees.map_err(|amount_error| BlockError::SummingMinerFees {
|
|
|
|
height,
|
|
|
|
hash,
|
|
|
|
source: amount_error,
|
|
|
|
})?;
|
2021-11-23 19:36:17 -08:00
|
|
|
check::miner_fees_are_valid(&block, network, block_miner_fees)?;
|
2021-11-23 07:31:56 -08: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");
|
2021-08-19 09:55:36 -07:00
|
|
|
|
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 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
|
2021-11-02 11:46:57 -07:00
|
|
|
.ready()
|
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()
|
|
|
|
}
|
|
|
|
}
|