fix: Stop the BlockVerifier modifying the state

We're going to make a new ChainVerifier that modifies the state.
This commit is contained in:
teor 2020-07-21 11:31:01 +10:00
parent db2eb80b3e
commit 53606dfae8
2 changed files with 19 additions and 159 deletions

View File

@ -1,4 +1,4 @@
//! Block verification and chain state updates for Zebra.
//! Block verification for Zebra.
//!
//! Verification occurs in multiple stages:
//! - getting blocks (disk- or network-bound)
@ -20,12 +20,14 @@ use std::{
sync::Arc,
task::{Context, Poll},
};
use tower::{buffer::Buffer, Service, ServiceExt};
use tower::{buffer::Buffer, Service};
use zebra_chain::block::{Block, BlockHeaderHash};
struct BlockVerifier<S> {
/// The underlying `ZebraState`, possibly wrapped in other services.
// TODO: contextual verification
#[allow(dead_code)]
state_service: S,
}
@ -35,7 +37,8 @@ type Error = Box<dyn error::Error + Send + Sync + 'static>;
/// The BlockVerifier service implementation.
///
/// After verification, blocks are added to the underlying state service.
/// The state service is only used for contextual verification.
/// (The `ChainVerifier` updates the state.)
impl<S> Service<Arc<Block>> for BlockVerifier<S>
where
S: Service<zebra_state::Request, Response = zebra_state::Response, Error = Error>
@ -50,17 +53,14 @@ where
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
// We don't expect the state to exert backpressure on verifier users,
// so we don't need to call `state_service.poll_ready()` here.
// 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.
Poll::Ready(Ok(()))
}
fn call(&mut self, block: Arc<Block>) -> Self::Future {
// TODO(jlusby): Error = Report, handle errors from state_service.
// TODO(teor):
// - handle chain reorgs
// - adjust state_service "unique block height" conditions
let mut state_service = self.state_service.clone();
async move {
// Since errors cause an early exit, try to do the
@ -71,18 +71,11 @@ where
block.header.is_equihash_solution_valid()?;
block.is_coinbase_first()?;
// `Tower::Buffer` requires a 1:1 relationship between `poll()`s
// and `call()`s, because it reserves a buffer slot in each
// `call()`.
let add_block = state_service
.ready_and()
.await?
.call(zebra_state::Request::AddBlock { block });
// TODO:
// - header verification
// - contextual verification
match add_block.await? {
zebra_state::Response::Added { hash } => Ok(hash),
_ => Err("adding block to zebra-state failed".into()),
}
Ok(block.as_ref().into())
}
.boxed()
}
@ -91,19 +84,16 @@ where
/// Return a block verification service, using the provided state service.
///
/// The block verifier holds a state service of type `S`, used as context for
/// block validation and to which newly verified blocks will be committed. This
/// state is pluggable to allow for testing or instrumentation.
/// block validation. This state is pluggable to allow for testing or
/// instrumentation.
///
/// The returned type is opaque to allow instrumentation or other wrappers, but
/// can be boxed for storage. It is also `Clone` to allow sharing of a
/// verification service.
///
/// This function should be called only once for a particular state service (and
/// the result be shared) rather than constructing multiple verification services
/// backed by the same state layer.
//
// Only used by tests and other modules
#[allow(dead_code)]
/// the result be shared, cloning if needed). Constructing multiple services
/// from the same underlying state might cause synchronisation bugs.
pub fn init<S>(
state_service: S,
) -> impl Service<

View File

@ -3,8 +3,8 @@
use super::*;
use chrono::{Duration, Utc};
use color_eyre::eyre::eyre;
use color_eyre::eyre::Report;
use color_eyre::eyre::{bail, eyre};
use std::sync::Arc;
use tower::{util::ServiceExt, Service};
@ -42,125 +42,6 @@ async fn verify() -> Result<(), Report> {
Ok(())
}
#[tokio::test]
async fn round_trip_test() -> Result<(), Report> {
round_trip().await
}
#[spandoc::spandoc]
async fn round_trip() -> Result<(), Report> {
zebra_test::init();
let block =
Arc::<Block>::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_415000_BYTES[..])?;
let hash: BlockHeaderHash = block.as_ref().into();
let mut state_service = zebra_state::in_memory::init();
let mut block_verifier = super::init(state_service.clone());
/// SPANDOC: Make sure the verifier service is ready
let ready_verifier_service = block_verifier.ready_and().await.map_err(|e| eyre!(e))?;
/// SPANDOC: Verify the block
let verify_response = ready_verifier_service
.call(block.clone())
.await
.map_err(|e| eyre!(e))?;
assert_eq!(verify_response, hash);
/// SPANDOC: Make sure the state service is ready
let ready_state_service = state_service.ready_and().await.map_err(|e| eyre!(e))?;
/// SPANDOC: Make sure the block was added to the state
let state_response = ready_state_service
.call(zebra_state::Request::GetBlock { hash })
.await
.map_err(|e| eyre!(e))?;
if let zebra_state::Response::Block {
block: returned_block,
} = state_response
{
assert_eq!(block, returned_block);
} else {
bail!("unexpected response kind: {:?}", state_response);
}
Ok(())
}
#[tokio::test]
async fn verify_fail_add_block_test() -> Result<(), Report> {
verify_fail_add_block().await
}
#[spandoc::spandoc]
async fn verify_fail_add_block() -> Result<(), Report> {
zebra_test::init();
let block =
Arc::<Block>::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_415000_BYTES[..])?;
let hash: BlockHeaderHash = block.as_ref().into();
let mut state_service = zebra_state::in_memory::init();
let mut block_verifier = super::init(state_service.clone());
/// SPANDOC: Make sure the verifier service is ready (1/2)
let ready_verifier_service = block_verifier.ready_and().await.map_err(|e| eyre!(e))?;
/// SPANDOC: Verify the block for the first time
let verify_response = ready_verifier_service
.call(block.clone())
.await
.map_err(|e| eyre!(e))?;
assert_eq!(verify_response, hash);
/// SPANDOC: Make sure the state service is ready (1/2)
let ready_state_service = state_service.ready_and().await.map_err(|e| eyre!(e))?;
/// SPANDOC: Make sure the block was added to the state
let state_response = ready_state_service
.call(zebra_state::Request::GetBlock { hash })
.await
.map_err(|e| eyre!(e))?;
if let zebra_state::Response::Block {
block: returned_block,
} = state_response
{
assert_eq!(block, returned_block);
} else {
bail!("unexpected response kind: {:?}", state_response);
}
/// SPANDOC: Make sure the verifier service is ready (2/2)
let ready_verifier_service = block_verifier.ready_and().await.map_err(|e| eyre!(e))?;
/// SPANDOC: Now try to add the block again, verify should fail
// TODO(teor): ignore duplicate block verifies?
// TODO(teor || jlusby): check error kind
ready_verifier_service
.call(block.clone())
.await
.unwrap_err();
/// SPANDOC: Make sure the state service is ready (2/2)
let ready_state_service = state_service.ready_and().await.map_err(|e| eyre!(e))?;
/// SPANDOC: But the state should still return the original block we added
let state_response = ready_state_service
.call(zebra_state::Request::GetBlock { hash })
.await
.map_err(|e| eyre!(e))?;
if let zebra_state::Response::Block {
block: returned_block,
} = state_response
{
assert_eq!(block, returned_block);
} else {
bail!("unexpected response kind: {:?}", state_response);
}
Ok(())
}
#[tokio::test]
async fn verify_fail_future_time_test() -> Result<(), Report> {
verify_fail_future_time().await
@ -173,7 +54,7 @@ async fn verify_fail_future_time() -> Result<(), Report> {
let mut block =
<Block>::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_415000_BYTES[..])?;
let mut state_service = zebra_state::in_memory::init();
let state_service = zebra_state::in_memory::init();
let mut block_verifier = super::init(state_service.clone());
// Modify the block's time
@ -197,17 +78,6 @@ async fn verify_fail_future_time() -> Result<(), Report> {
.await
.unwrap_err();
/// SPANDOC: Make sure the state service is ready (2/2)
let ready_state_service = state_service.ready_and().await.map_err(|e| eyre!(e))?;
/// SPANDOC: Now make sure the block isn't in the state
// TODO(teor || jlusby): check error kind
ready_state_service
.call(zebra_state::Request::GetBlock {
hash: arc_block.as_ref().into(),
})
.await
.unwrap_err();
Ok(())
}