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:
parent
8ba9d0114b
commit
7403897fda
|
@ -27,6 +27,8 @@ use crate::{
|
|||
|
||||
mod check;
|
||||
mod memory_state;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
mod utxo;
|
||||
|
||||
// todo: put this somewhere
|
||||
|
|
|
@ -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(())
|
||||
}
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue