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 check;
|
||||||
mod memory_state;
|
mod memory_state;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
mod utxo;
|
mod utxo;
|
||||||
|
|
||||||
// todo: put this somewhere
|
// 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}")]
|
#[error("ErrorChecker Error: {0}")]
|
||||||
struct ErrorCheckerError(Error);
|
struct ErrorCheckerError(Error);
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub struct Transcript<R, S, I>
|
pub struct Transcript<R, S, I>
|
||||||
where
|
where
|
||||||
I: Iterator<Item = (R, Result<S, TransError>)>,
|
I: Iterator<Item = (R, Result<S, TransError>)>,
|
||||||
|
@ -58,12 +59,14 @@ where
|
||||||
messages: I,
|
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
|
where
|
||||||
I: Iterator<Item = (R, Result<S, TransError>)>,
|
I: IntoIterator<Item = (R, Result<S, TransError>)>,
|
||||||
{
|
{
|
||||||
fn from(messages: I) -> Self {
|
fn from(messages: I) -> Self {
|
||||||
Self { messages }
|
Self {
|
||||||
|
messages: messages.into_iter(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue