fix: Stop the BlockVerifier modifying the state
We're going to make a new ChainVerifier that modifies the state.
This commit is contained in:
parent
db2eb80b3e
commit
53606dfae8
|
@ -1,4 +1,4 @@
|
||||||
//! Block verification and chain state updates for Zebra.
|
//! Block verification for Zebra.
|
||||||
//!
|
//!
|
||||||
//! Verification occurs in multiple stages:
|
//! Verification occurs in multiple stages:
|
||||||
//! - getting blocks (disk- or network-bound)
|
//! - getting blocks (disk- or network-bound)
|
||||||
|
@ -20,12 +20,14 @@ use std::{
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
use tower::{buffer::Buffer, Service, ServiceExt};
|
use tower::{buffer::Buffer, Service};
|
||||||
|
|
||||||
use zebra_chain::block::{Block, BlockHeaderHash};
|
use zebra_chain::block::{Block, BlockHeaderHash};
|
||||||
|
|
||||||
struct BlockVerifier<S> {
|
struct BlockVerifier<S> {
|
||||||
/// The underlying `ZebraState`, possibly wrapped in other services.
|
/// The underlying `ZebraState`, possibly wrapped in other services.
|
||||||
|
// TODO: contextual verification
|
||||||
|
#[allow(dead_code)]
|
||||||
state_service: S,
|
state_service: S,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +37,8 @@ type Error = Box<dyn error::Error + Send + Sync + 'static>;
|
||||||
|
|
||||||
/// The BlockVerifier service implementation.
|
/// 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>
|
impl<S> Service<Arc<Block>> for BlockVerifier<S>
|
||||||
where
|
where
|
||||||
S: Service<zebra_state::Request, Response = zebra_state::Response, Error = Error>
|
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>>;
|
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
|
||||||
|
|
||||||
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
// We don't expect the state to exert backpressure on verifier users,
|
// We use the state for contextual verification, and we expect those
|
||||||
// so we don't need to call `state_service.poll_ready()` here.
|
// queries to be fast. So we don't need to call
|
||||||
|
// `state_service.poll_ready()` here.
|
||||||
Poll::Ready(Ok(()))
|
Poll::Ready(Ok(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self, block: Arc<Block>) -> Self::Future {
|
fn call(&mut self, block: Arc<Block>) -> Self::Future {
|
||||||
// TODO(jlusby): Error = Report, handle errors from state_service.
|
// 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 {
|
async move {
|
||||||
// Since errors cause an early exit, try to do the
|
// Since errors cause an early exit, try to do the
|
||||||
|
@ -71,18 +71,11 @@ where
|
||||||
block.header.is_equihash_solution_valid()?;
|
block.header.is_equihash_solution_valid()?;
|
||||||
block.is_coinbase_first()?;
|
block.is_coinbase_first()?;
|
||||||
|
|
||||||
// `Tower::Buffer` requires a 1:1 relationship between `poll()`s
|
// TODO:
|
||||||
// and `call()`s, because it reserves a buffer slot in each
|
// - header verification
|
||||||
// `call()`.
|
// - contextual verification
|
||||||
let add_block = state_service
|
|
||||||
.ready_and()
|
|
||||||
.await?
|
|
||||||
.call(zebra_state::Request::AddBlock { block });
|
|
||||||
|
|
||||||
match add_block.await? {
|
Ok(block.as_ref().into())
|
||||||
zebra_state::Response::Added { hash } => Ok(hash),
|
|
||||||
_ => Err("adding block to zebra-state failed".into()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
@ -91,19 +84,16 @@ where
|
||||||
/// Return a block verification service, using the provided state service.
|
/// Return a block verification service, using the provided state service.
|
||||||
///
|
///
|
||||||
/// The block verifier holds a state service of type `S`, used as context for
|
/// 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
|
/// block validation. This state is pluggable to allow for testing or
|
||||||
/// state is pluggable to allow for testing or instrumentation.
|
/// instrumentation.
|
||||||
///
|
///
|
||||||
/// The returned type is opaque to allow instrumentation or other wrappers, but
|
/// 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
|
/// can be boxed for storage. It is also `Clone` to allow sharing of a
|
||||||
/// verification service.
|
/// verification service.
|
||||||
///
|
///
|
||||||
/// This function should be called only once for a particular state service (and
|
/// This function should be called only once for a particular state service (and
|
||||||
/// the result be shared) rather than constructing multiple verification services
|
/// the result be shared, cloning if needed). Constructing multiple services
|
||||||
/// backed by the same state layer.
|
/// from the same underlying state might cause synchronisation bugs.
|
||||||
//
|
|
||||||
// Only used by tests and other modules
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn init<S>(
|
pub fn init<S>(
|
||||||
state_service: S,
|
state_service: S,
|
||||||
) -> impl Service<
|
) -> impl Service<
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use chrono::{Duration, Utc};
|
use chrono::{Duration, Utc};
|
||||||
|
use color_eyre::eyre::eyre;
|
||||||
use color_eyre::eyre::Report;
|
use color_eyre::eyre::Report;
|
||||||
use color_eyre::eyre::{bail, eyre};
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tower::{util::ServiceExt, Service};
|
use tower::{util::ServiceExt, Service};
|
||||||
|
|
||||||
|
@ -42,125 +42,6 @@ async fn verify() -> Result<(), Report> {
|
||||||
Ok(())
|
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]
|
#[tokio::test]
|
||||||
async fn verify_fail_future_time_test() -> Result<(), Report> {
|
async fn verify_fail_future_time_test() -> Result<(), Report> {
|
||||||
verify_fail_future_time().await
|
verify_fail_future_time().await
|
||||||
|
@ -173,7 +54,7 @@ async fn verify_fail_future_time() -> Result<(), Report> {
|
||||||
let mut block =
|
let mut block =
|
||||||
<Block>::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_415000_BYTES[..])?;
|
<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());
|
let mut block_verifier = super::init(state_service.clone());
|
||||||
|
|
||||||
// Modify the block's time
|
// Modify the block's time
|
||||||
|
@ -197,17 +78,6 @@ async fn verify_fail_future_time() -> Result<(), Report> {
|
||||||
.await
|
.await
|
||||||
.unwrap_err();
|
.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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue