186 lines
5.6 KiB
Rust
186 lines
5.6 KiB
Rust
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<Item = Arc<Block>>,
|
|
) -> BoxService<Request, Response, BoxError> {
|
|
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<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()))),
|
|
));
|
|
|
|
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<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(())
|
|
}
|