From 7403897fdac22910c5ca726d29fd4835b3a275fe Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Fri, 13 Nov 2020 10:19:47 -0800 Subject: [PATCH] Add transcript tests as described in the state service tracking issue (#1281) * Add transcript test for requests while state is empty * Add happy path test for each query once the state is populated * let populate logic handle out of order blocks --- zebra-state/src/service.rs | 2 + zebra-state/src/service/tests.rs | 182 +++++++++++++++++++++++++++++++ zebra-test/src/transcript.rs | 9 +- 3 files changed, 190 insertions(+), 3 deletions(-) create mode 100644 zebra-state/src/service/tests.rs diff --git a/zebra-state/src/service.rs b/zebra-state/src/service.rs index 2a6430e3..1deefb1b 100644 --- a/zebra-state/src/service.rs +++ b/zebra-state/src/service.rs @@ -27,6 +27,8 @@ use crate::{ mod check; mod memory_state; +#[cfg(test)] +mod tests; mod utxo; // todo: put this somewhere diff --git a/zebra-state/src/service/tests.rs b/zebra-state/src/service/tests.rs new file mode 100644 index 00000000..1acf1590 --- /dev/null +++ b/zebra-state/src/service/tests.rs @@ -0,0 +1,182 @@ +use std::sync::Arc; + +use futures::stream::FuturesUnordered; +use tower::{util::BoxService, Service, ServiceExt}; +use zebra_chain::{ + block::Block, parameters::Network, serialization::ZcashDeserializeInto, transaction, + transparent, +}; +use zebra_test::{prelude::*, transcript::Transcript}; + +use crate::{init, BoxError, Config, Request, Response}; + +const LAST_BLOCK_HEIGHT: u32 = 10; + +async fn populated_state( + blocks: impl IntoIterator>, +) -> BoxService { + let requests = blocks + .into_iter() + .map(|block| Request::CommitFinalizedBlock { block }); + + let config = Config::ephemeral(); + let network = Network::Mainnet; + let mut state = init(config, network); + + let mut responses = FuturesUnordered::new(); + + for request in requests { + let rsp = state.ready_and().await.unwrap().call(request); + responses.push(rsp); + } + + use futures::StreamExt; + while let Some(rsp) = responses.next().await { + rsp.expect("blocks should commit just fine"); + } + + state +} + +async fn test_populated_state_responds_correctly( + mut state: BoxService, +) -> Result<()> { + let blocks = zebra_test::vectors::MAINNET_BLOCKS + .range(0..=LAST_BLOCK_HEIGHT) + .map(|(_, block_bytes)| block_bytes.zcash_deserialize_into::>().unwrap()); + + for (ind, block) in blocks.into_iter().enumerate() { + let mut transcript = vec![]; + let height = block.coinbase_height().unwrap(); + let hash = block.hash(); + + transcript.push(( + Request::Block(hash.into()), + Ok(Response::Block(Some(block.clone()))), + )); + + transcript.push(( + Request::Block(height.into()), + Ok(Response::Block(Some(block.clone()))), + )); + + transcript.push(( + Request::Depth(block.hash()), + Ok(Response::Depth(Some(LAST_BLOCK_HEIGHT - height.0))), + )); + + if ind == LAST_BLOCK_HEIGHT as usize { + transcript.push((Request::Tip, Ok(Response::Tip(Some((height, hash)))))); + } + + // Consensus-critical bug in zcashd: transactions in the genesis block + // are ignored. + if height.0 != 0 { + for transaction in &block.transactions { + let transaction_hash = transaction.hash(); + + transcript.push(( + Request::Transaction(transaction_hash), + Ok(Response::Transaction(Some(transaction.clone()))), + )); + + for (index, output) in transaction.outputs().iter().enumerate() { + let outpoint = transparent::OutPoint { + hash: transaction_hash, + index: index as _, + }; + + transcript.push(( + Request::AwaitUtxo(outpoint), + Ok(Response::Utxo(output.clone())), + )); + } + } + } + + let transcript = Transcript::from(transcript); + transcript.check(&mut state).await?; + } + + Ok(()) +} + +#[tokio::main] +async fn populate_and_check(blocks: Vec>) -> Result<()> { + let state = populated_state(blocks).await; + test_populated_state_responds_correctly(state).await?; + Ok(()) +} + +fn out_of_order_committing_strategy() -> BoxedStrategy>> { + let blocks = zebra_test::vectors::MAINNET_BLOCKS + .range(0..=LAST_BLOCK_HEIGHT) + .map(|(_, block_bytes)| block_bytes.zcash_deserialize_into::>().unwrap()) + .collect::>(); + + Just(blocks).prop_shuffle().boxed() +} + +#[tokio::test] +async fn empty_state_still_responds_to_requests() -> Result<()> { + zebra_test::init(); + + let block = + zebra_test::vectors::BLOCK_MAINNET_419200_BYTES.zcash_deserialize_into::>()?; + + let iter = vec![ + // No checks for CommitBlock or CommitFinalizedBlock because empty state + // precondition doesn't matter to them + (Request::Depth(block.hash()), Ok(Response::Depth(None))), + (Request::Tip, Ok(Response::Tip(None))), + (Request::BlockLocator, Ok(Response::BlockLocator(vec![]))), + ( + Request::Transaction(transaction::Hash([0; 32])), + Ok(Response::Transaction(None)), + ), + ( + Request::Block(block.hash().into()), + Ok(Response::Block(None)), + ), + ( + Request::Block(block.coinbase_height().unwrap().into()), + Ok(Response::Block(None)), + ), + // No check for AwaitUTXO because it will wait if the UTXO isn't present + ] + .into_iter(); + let transcript = Transcript::from(iter); + + let config = Config::ephemeral(); + let network = Network::Mainnet; + let state = init(config, network); + + transcript.check(state).await?; + + Ok(()) +} + +#[test] +fn state_behaves_when_blocks_are_committed_in_order() -> Result<()> { + zebra_test::init(); + + let blocks = zebra_test::vectors::MAINNET_BLOCKS + .range(0..=LAST_BLOCK_HEIGHT) + .map(|(_, block_bytes)| block_bytes.zcash_deserialize_into::>().unwrap()) + .collect(); + + populate_and_check(blocks)?; + + Ok(()) +} + +#[test] +fn state_behaves_when_blocks_are_committed_out_of_order() -> Result<()> { + zebra_test::init(); + + proptest!(|(blocks in out_of_order_committing_strategy())| { + populate_and_check(blocks).unwrap(); + }); + + Ok(()) +} diff --git a/zebra-test/src/transcript.rs b/zebra-test/src/transcript.rs index 8c9a2d44..feea1b0c 100644 --- a/zebra-test/src/transcript.rs +++ b/zebra-test/src/transcript.rs @@ -51,6 +51,7 @@ impl TransError { #[error("ErrorChecker Error: {0}")] struct ErrorCheckerError(Error); +#[must_use] pub struct Transcript where I: Iterator)>, @@ -58,12 +59,14 @@ where messages: I, } -impl From for Transcript +impl From for Transcript where - I: Iterator)>, + I: IntoIterator)>, { fn from(messages: I) -> Self { - Self { messages } + Self { + messages: messages.into_iter(), + } } }