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
This commit is contained in:
Jane Lusby 2020-11-13 10:19:47 -08:00 committed by GitHub
parent 8ba9d0114b
commit 7403897fda
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 190 additions and 3 deletions

View File

@ -27,6 +27,8 @@ use crate::{
mod check;
mod memory_state;
#[cfg(test)]
mod tests;
mod utxo;
// todo: put this somewhere

View File

@ -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<Item = Arc<Block>>,
) -> BoxService<Request, Response, BoxError> {
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<Request, Response, BoxError>,
) -> Result<()> {
let blocks = zebra_test::vectors::MAINNET_BLOCKS
.range(0..=LAST_BLOCK_HEIGHT)
.map(|(_, block_bytes)| block_bytes.zcash_deserialize_into::<Arc<Block>>().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<Arc<Block>>) -> Result<()> {
let state = populated_state(blocks).await;
test_populated_state_responds_correctly(state).await?;
Ok(())
}
fn out_of_order_committing_strategy() -> BoxedStrategy<Vec<Arc<Block>>> {
let blocks = zebra_test::vectors::MAINNET_BLOCKS
.range(0..=LAST_BLOCK_HEIGHT)
.map(|(_, block_bytes)| block_bytes.zcash_deserialize_into::<Arc<Block>>().unwrap())
.collect::<Vec<_>>();
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::<Arc<Block>>()?;
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::<Arc<Block>>().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(())
}

View File

@ -51,6 +51,7 @@ impl TransError {
#[error("ErrorChecker Error: {0}")]
struct ErrorCheckerError(Error);
#[must_use]
pub struct Transcript<R, S, I>
where
I: Iterator<Item = (R, Result<S, TransError>)>,
@ -58,12 +59,14 @@ where
messages: I,
}
impl<R, S, I> From<I> for Transcript<R, S, I>
impl<R, S, I> From<I> for Transcript<R, S, I::IntoIter>
where
I: Iterator<Item = (R, Result<S, TransError>)>,
I: IntoIterator<Item = (R, Result<S, TransError>)>,
{
fn from(messages: I) -> Self {
Self { messages }
Self {
messages: messages.into_iter(),
}
}
}