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, Utxo}; const LAST_BLOCK_HEIGHT: u32 = 10; async fn populated_state( blocks: impl IntoIterator>, ) -> BoxService { let requests = blocks .into_iter() .map(|block| Request::CommitFinalizedBlock(block.into())); 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()))), )); let from_coinbase = transaction.is_coinbase(); for (index, output) in transaction.outputs().iter().cloned().enumerate() { let outpoint = transparent::OutPoint { hash: transaction_hash, index: index as _, }; let utxo = Utxo { output, height, from_coinbase, }; transcript.push((Request::AwaitUtxo(outpoint), Ok(Response::Utxo(utxo)))); } } } 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(()) }