5. change(state): split ReadStateService requests into a ReadRequest enum (#3866)
* Split out ReadRequest and ReadResponse state service enums * Simplify RPC test vectors * Split state requests into Request and ReadRequest * Make zebra-rpc use the new state ReadRequest
This commit is contained in:
parent
b9640fbb55
commit
39dfca8e64
|
|
@ -110,8 +110,8 @@ pub struct RpcImpl<Mempool, State, Tip>
|
||||||
where
|
where
|
||||||
Mempool: Service<mempool::Request, Response = mempool::Response, Error = BoxError>,
|
Mempool: Service<mempool::Request, Response = mempool::Response, Error = BoxError>,
|
||||||
State: Service<
|
State: Service<
|
||||||
zebra_state::Request,
|
zebra_state::ReadRequest,
|
||||||
Response = zebra_state::Response,
|
Response = zebra_state::ReadResponse,
|
||||||
Error = zebra_state::BoxError,
|
Error = zebra_state::BoxError,
|
||||||
>,
|
>,
|
||||||
Tip: ChainTip,
|
Tip: ChainTip,
|
||||||
|
|
@ -137,8 +137,8 @@ impl<Mempool, State, Tip> RpcImpl<Mempool, State, Tip>
|
||||||
where
|
where
|
||||||
Mempool: Service<mempool::Request, Response = mempool::Response, Error = BoxError>,
|
Mempool: Service<mempool::Request, Response = mempool::Response, Error = BoxError>,
|
||||||
State: Service<
|
State: Service<
|
||||||
zebra_state::Request,
|
zebra_state::ReadRequest,
|
||||||
Response = zebra_state::Response,
|
Response = zebra_state::ReadResponse,
|
||||||
Error = zebra_state::BoxError,
|
Error = zebra_state::BoxError,
|
||||||
>,
|
>,
|
||||||
Tip: ChainTip + Send + Sync,
|
Tip: ChainTip + Send + Sync,
|
||||||
|
|
@ -170,8 +170,8 @@ where
|
||||||
tower::Service<mempool::Request, Response = mempool::Response, Error = BoxError> + 'static,
|
tower::Service<mempool::Request, Response = mempool::Response, Error = BoxError> + 'static,
|
||||||
Mempool::Future: Send,
|
Mempool::Future: Send,
|
||||||
State: Service<
|
State: Service<
|
||||||
zebra_state::Request,
|
zebra_state::ReadRequest,
|
||||||
Response = zebra_state::Response,
|
Response = zebra_state::ReadResponse,
|
||||||
Error = zebra_state::BoxError,
|
Error = zebra_state::BoxError,
|
||||||
> + Clone
|
> + Clone
|
||||||
+ Send
|
+ Send
|
||||||
|
|
@ -257,7 +257,8 @@ where
|
||||||
data: None,
|
data: None,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let request = zebra_state::Request::Block(zebra_state::HashOrHeight::Height(height));
|
let request =
|
||||||
|
zebra_state::ReadRequest::Block(zebra_state::HashOrHeight::Height(height));
|
||||||
let response = state
|
let response = state
|
||||||
.ready()
|
.ready()
|
||||||
.and_then(|service| service.call(request))
|
.and_then(|service| service.call(request))
|
||||||
|
|
@ -269,8 +270,8 @@ where
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
match response {
|
match response {
|
||||||
zebra_state::Response::Block(Some(block)) => Ok(GetBlock(block.into())),
|
zebra_state::ReadResponse::Block(Some(block)) => Ok(GetBlock(block.into())),
|
||||||
zebra_state::Response::Block(None) => Err(Error {
|
zebra_state::ReadResponse::Block(None) => Err(Error {
|
||||||
code: ErrorCode::ServerError(0),
|
code: ErrorCode::ServerError(0),
|
||||||
message: "Block not found".to_string(),
|
message: "Block not found".to_string(),
|
||||||
data: None,
|
data: None,
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,6 @@ use zebra_test::mock_service::MockService;
|
||||||
|
|
||||||
use super::super::*;
|
use super::super::*;
|
||||||
|
|
||||||
// Number of blocks to populate state with
|
|
||||||
const NUMBER_OF_BLOCKS: u32 = 10;
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn rpc_getinfo() {
|
async fn rpc_getinfo() {
|
||||||
zebra_test::init();
|
zebra_test::init();
|
||||||
|
|
@ -51,10 +48,10 @@ async fn rpc_getinfo() {
|
||||||
async fn rpc_getblock() {
|
async fn rpc_getblock() {
|
||||||
zebra_test::init();
|
zebra_test::init();
|
||||||
|
|
||||||
// Put the first `NUMBER_OF_BLOCKS` blocks in a vector
|
// Create a continuous chain of mainnet blocks from genesis
|
||||||
let blocks: Vec<Arc<Block>> = zebra_test::vectors::MAINNET_BLOCKS
|
let blocks: Vec<Arc<Block>> = zebra_test::vectors::CONTINUOUS_MAINNET_BLOCKS
|
||||||
.range(0..=NUMBER_OF_BLOCKS)
|
.iter()
|
||||||
.map(|(_, block_bytes)| block_bytes.zcash_deserialize_into::<Arc<Block>>().unwrap())
|
.map(|(_height, block_bytes)| block_bytes.zcash_deserialize_into().unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
|
let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
|
||||||
|
|
@ -114,17 +111,15 @@ async fn rpc_getblock_error() {
|
||||||
async fn rpc_getbestblockhash() {
|
async fn rpc_getbestblockhash() {
|
||||||
zebra_test::init();
|
zebra_test::init();
|
||||||
|
|
||||||
// Put `NUMBER_OF_BLOCKS` blocks in a vector
|
// Create a continuous chain of mainnet blocks from genesis
|
||||||
let blocks: Vec<Arc<Block>> = zebra_test::vectors::MAINNET_BLOCKS
|
let blocks: Vec<Arc<Block>> = zebra_test::vectors::CONTINUOUS_MAINNET_BLOCKS
|
||||||
.range(0..=NUMBER_OF_BLOCKS)
|
.iter()
|
||||||
.map(|(_, block_bytes)| block_bytes.zcash_deserialize_into::<Arc<Block>>().unwrap())
|
.map(|(_height, block_bytes)| block_bytes.zcash_deserialize_into().unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Get the hash of the block at the tip using hardcoded block tip bytes.
|
// Get the hash of the block at the tip using hardcoded block tip bytes.
|
||||||
// We want to test the RPC response is equal to this hash
|
// We want to test the RPC response is equal to this hash
|
||||||
let tip_block: Block = zebra_test::vectors::BLOCK_MAINNET_10_BYTES
|
let tip_block = blocks.last().unwrap();
|
||||||
.zcash_deserialize_into()
|
|
||||||
.unwrap();
|
|
||||||
let tip_block_hash = tip_block.hash();
|
let tip_block_hash = tip_block.hash();
|
||||||
|
|
||||||
// Get a mempool handle
|
// Get a mempool handle
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,8 @@ impl RpcServer {
|
||||||
+ 'static,
|
+ 'static,
|
||||||
Mempool::Future: Send,
|
Mempool::Future: Send,
|
||||||
State: Service<
|
State: Service<
|
||||||
zebra_state::Request,
|
zebra_state::ReadRequest,
|
||||||
Response = zebra_state::Response,
|
Response = zebra_state::ReadResponse,
|
||||||
Error = zebra_state::BoxError,
|
Error = zebra_state::BoxError,
|
||||||
> + Clone
|
> + Clone
|
||||||
+ Send
|
+ Send
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,8 @@ mod tests;
|
||||||
pub use config::Config;
|
pub use config::Config;
|
||||||
pub use constants::MAX_BLOCK_REORG_HEIGHT;
|
pub use constants::MAX_BLOCK_REORG_HEIGHT;
|
||||||
pub use error::{BoxError, CloneError, CommitBlockError, ValidateContextError};
|
pub use error::{BoxError, CloneError, CommitBlockError, ValidateContextError};
|
||||||
pub use request::{FinalizedBlock, HashOrHeight, PreparedBlock, Request};
|
pub use request::{FinalizedBlock, HashOrHeight, PreparedBlock, ReadRequest, Request};
|
||||||
pub use response::Response;
|
pub use response::{ReadResponse, Response};
|
||||||
pub use service::{
|
pub use service::{
|
||||||
chain_tip::{ChainTipChange, LatestChainTip, TipAction},
|
chain_tip::{ChainTipChange, LatestChainTip, TipAction},
|
||||||
init,
|
init,
|
||||||
|
|
@ -42,7 +42,7 @@ pub use service::{
|
||||||
pub use service::{
|
pub use service::{
|
||||||
arbitrary::populated_state,
|
arbitrary::populated_state,
|
||||||
chain_tip::{ChainTipBlock, ChainTipSender},
|
chain_tip::{ChainTipBlock, ChainTipSender},
|
||||||
init_test,
|
init_test, init_test_services,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) use request::ContextuallyValidBlock;
|
pub(crate) use request::ContextuallyValidBlock;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
//! State [`tower::Service`] request types.
|
||||||
|
|
||||||
use std::{collections::HashMap, sync::Arc};
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
|
|
@ -229,7 +231,7 @@ impl From<ContextuallyValidBlock> for FinalizedBlock {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
/// A query about or modification to the chain state.
|
/// A query about or modification to the chain state, via the [`StateService`].
|
||||||
pub enum Request {
|
pub enum Request {
|
||||||
/// Performs contextual validation of the given block, committing it to the
|
/// Performs contextual validation of the given block, committing it to the
|
||||||
/// state if successful.
|
/// state if successful.
|
||||||
|
|
@ -377,3 +379,26 @@ pub enum Request {
|
||||||
stop: Option<block::Hash>,
|
stop: Option<block::Hash>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
/// A read-only query about the chain state, via the [`ReadStateService`].
|
||||||
|
pub enum ReadRequest {
|
||||||
|
/// Looks up a block by hash or height in the current best chain.
|
||||||
|
///
|
||||||
|
/// Returns
|
||||||
|
///
|
||||||
|
/// * [`Response::Block(Some(Arc<Block>))`](Response::Block) if the block is in the best chain;
|
||||||
|
/// * [`Response::Block(None)`](Response::Block) otherwise.
|
||||||
|
///
|
||||||
|
/// Note: the [`HashOrHeight`] can be constructed from a [`block::Hash`] or
|
||||||
|
/// [`block::Height`] using `.into()`.
|
||||||
|
Block(HashOrHeight),
|
||||||
|
|
||||||
|
/// Looks up a transaction by hash in the current best chain.
|
||||||
|
///
|
||||||
|
/// Returns
|
||||||
|
///
|
||||||
|
/// * [`Response::Transaction(Some(Arc<Transaction>))`](Response::Transaction) if the transaction is in the best chain;
|
||||||
|
/// * [`Response::Transaction(None)`](Response::Transaction) otherwise.
|
||||||
|
Transaction(transaction::Hash),
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
|
//! State [`tower::Service`] response types.
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
block::{self, Block},
|
block::{self, Block},
|
||||||
transaction::Transaction,
|
transaction::Transaction,
|
||||||
|
|
@ -11,7 +14,7 @@ use zebra_chain::{
|
||||||
use crate::Request;
|
use crate::Request;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
/// A response to a state [`Request`].
|
/// A response to a [`StateService`] [`Request`].
|
||||||
pub enum Response {
|
pub enum Response {
|
||||||
/// Response to [`Request::CommitBlock`] indicating that a block was
|
/// Response to [`Request::CommitBlock`] indicating that a block was
|
||||||
/// successfully committed to the state.
|
/// successfully committed to the state.
|
||||||
|
|
@ -41,3 +44,13 @@ pub enum Response {
|
||||||
/// The response to a `FindBlockHeaders` request.
|
/// The response to a `FindBlockHeaders` request.
|
||||||
BlockHeaders(Vec<block::CountedHeader>),
|
BlockHeaders(Vec<block::CountedHeader>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
/// A response to a read-only [`ReadStateService`] [`ReadRequest`].
|
||||||
|
pub enum ReadResponse {
|
||||||
|
/// Response to [`ReadRequest::Block`] with the specified block.
|
||||||
|
Block(Option<Arc<Block>>),
|
||||||
|
|
||||||
|
/// Response to [`ReadRequest::Transaction`] with the specified transaction.
|
||||||
|
Transaction(Option<Arc<Transaction>>),
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,8 @@ use crate::{
|
||||||
pending_utxos::PendingUtxos,
|
pending_utxos::PendingUtxos,
|
||||||
watch_receiver::WatchReceiver,
|
watch_receiver::WatchReceiver,
|
||||||
},
|
},
|
||||||
BoxError, CloneError, CommitBlockError, Config, FinalizedBlock, PreparedBlock, Request,
|
BoxError, CloneError, CommitBlockError, Config, FinalizedBlock, PreparedBlock, ReadRequest,
|
||||||
Response, ValidateContextError,
|
ReadResponse, Request, Response, ValidateContextError,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod block_iter;
|
pub mod block_iter;
|
||||||
|
|
@ -902,8 +902,8 @@ impl Service<Request> for StateService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Service<Request> for ReadStateService {
|
impl Service<ReadRequest> for ReadStateService {
|
||||||
type Response = Response;
|
type Response = ReadResponse;
|
||||||
type Error = BoxError;
|
type Error = BoxError;
|
||||||
type Future =
|
type Future =
|
||||||
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
|
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
|
||||||
|
|
@ -913,10 +913,19 @@ impl Service<Request> for ReadStateService {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(name = "read_state", skip(self))]
|
#[instrument(name = "read_state", skip(self))]
|
||||||
fn call(&mut self, req: Request) -> Self::Future {
|
fn call(&mut self, req: ReadRequest) -> Self::Future {
|
||||||
match req {
|
match req {
|
||||||
|
// TODO: implement these new ReadRequests for lightwalletd, as part of these tickets
|
||||||
|
|
||||||
|
// z_get_tree_state (#3156)
|
||||||
|
|
||||||
|
// depends on transparent address indexes (#3150)
|
||||||
|
// get_address_tx_ids (#3147)
|
||||||
|
// get_address_balance (#3157)
|
||||||
|
// get_address_utxos (#3158)
|
||||||
|
|
||||||
// Used by get_block RPC.
|
// Used by get_block RPC.
|
||||||
Request::Block(hash_or_height) => {
|
ReadRequest::Block(hash_or_height) => {
|
||||||
metrics::counter!(
|
metrics::counter!(
|
||||||
"state.requests",
|
"state.requests",
|
||||||
1,
|
1,
|
||||||
|
|
@ -931,13 +940,13 @@ impl Service<Request> for ReadStateService {
|
||||||
read::block(best_chain, &state.db, hash_or_height)
|
read::block(best_chain, &state.db, hash_or_height)
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(Response::Block(block))
|
Ok(ReadResponse::Block(block))
|
||||||
}
|
}
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
// For the get_raw_transaction RPC, to be implemented in #3145.
|
// For the get_raw_transaction RPC, to be implemented in #3145.
|
||||||
Request::Transaction(hash) => {
|
ReadRequest::Transaction(hash) => {
|
||||||
metrics::counter!(
|
metrics::counter!(
|
||||||
"state.requests",
|
"state.requests",
|
||||||
1,
|
1,
|
||||||
|
|
@ -952,60 +961,10 @@ impl Service<Request> for ReadStateService {
|
||||||
read::transaction(best_chain, &state.db, hash)
|
read::transaction(best_chain, &state.db, hash)
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(Response::Transaction(transaction))
|
Ok(ReadResponse::Transaction(transaction))
|
||||||
}
|
}
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: split the Request enum, then implement these new ReadRequests for lightwalletd
|
|
||||||
// as part of these tickets
|
|
||||||
|
|
||||||
// z_get_tree_state (#3156)
|
|
||||||
|
|
||||||
// depends on transparent address indexes (#3150)
|
|
||||||
// get_address_tx_ids (#3147)
|
|
||||||
// get_address_balance (#3157)
|
|
||||||
// get_address_utxos (#3158)
|
|
||||||
|
|
||||||
// Out of Scope
|
|
||||||
// TODO: delete when splitting the Request enum
|
|
||||||
|
|
||||||
// Use ChainTip instead.
|
|
||||||
Request::Tip => unreachable!("ReadStateService doesn't need to Tip"),
|
|
||||||
|
|
||||||
// These requests don't need better performance at the moment.
|
|
||||||
Request::FindBlockHashes {
|
|
||||||
known_blocks: _,
|
|
||||||
stop: _,
|
|
||||||
} => {
|
|
||||||
unreachable!("ReadStateService doesn't need to FindBlockHashes")
|
|
||||||
}
|
|
||||||
Request::FindBlockHeaders {
|
|
||||||
known_blocks: _,
|
|
||||||
stop: _,
|
|
||||||
} => {
|
|
||||||
unreachable!("ReadStateService doesn't need to FindBlockHeaders")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some callers of this request need to wait for queued blocks.
|
|
||||||
Request::Depth(_hash) => unreachable!("ReadStateService could change depth behaviour"),
|
|
||||||
|
|
||||||
// This request needs to wait for queued blocks.
|
|
||||||
Request::BlockLocator => {
|
|
||||||
unreachable!("ReadStateService should not be used for block locators")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Impossible Requests
|
|
||||||
|
|
||||||
// The read-only service doesn't have the shared internal state
|
|
||||||
// needed to await UTXOs.
|
|
||||||
Request::AwaitUtxo(_outpoint) => unreachable!("ReadStateService can't await UTXOs"),
|
|
||||||
|
|
||||||
// The read-only service can't write.
|
|
||||||
Request::CommitBlock(_prepared) => unreachable!("ReadStateService can't commit blocks"),
|
|
||||||
Request::CommitFinalizedBlock(_finalized) => {
|
|
||||||
unreachable!("ReadStateService can't commit blocks")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1042,12 +1001,40 @@ pub fn init(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize a state service with an ephemeral [`Config`] and a buffer with a single slot.
|
/// Returns a [`StateService`] with an ephemeral [`Config`] and a buffer with a single slot.
|
||||||
///
|
///
|
||||||
/// This can be used to create a state service for testing. See also [`init`].
|
/// This can be used to create a state service for testing.
|
||||||
|
///
|
||||||
|
/// See also [`init`].
|
||||||
#[cfg(any(test, feature = "proptest-impl"))]
|
#[cfg(any(test, feature = "proptest-impl"))]
|
||||||
pub fn init_test(network: Network) -> Buffer<BoxService<Request, Response, BoxError>, Request> {
|
pub fn init_test(network: Network) -> Buffer<BoxService<Request, Response, BoxError>, Request> {
|
||||||
let (state_service, _, _, _) = StateService::new(Config::ephemeral(), network);
|
let (state_service, _, _, _) = StateService::new(Config::ephemeral(), network);
|
||||||
|
|
||||||
Buffer::new(BoxService::new(state_service), 1)
|
Buffer::new(BoxService::new(state_service), 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Initializes a state service with an ephemeral [`Config`] and a buffer with a single slot,
|
||||||
|
/// then returns the read-write service, read-only service, and tip watch channels.
|
||||||
|
///
|
||||||
|
/// This can be used to create a state service for testing. See also [`init`].
|
||||||
|
#[cfg(any(test, feature = "proptest-impl"))]
|
||||||
|
pub fn init_test_services(
|
||||||
|
network: Network,
|
||||||
|
) -> (
|
||||||
|
Buffer<BoxService<Request, Response, BoxError>, Request>,
|
||||||
|
ReadStateService,
|
||||||
|
LatestChainTip,
|
||||||
|
ChainTipChange,
|
||||||
|
) {
|
||||||
|
let (state_service, read_state_service, latest_chain_tip, chain_tip_change) =
|
||||||
|
StateService::new(Config::ephemeral(), network);
|
||||||
|
|
||||||
|
let state_service = Buffer::new(BoxService::new(state_service), 1);
|
||||||
|
|
||||||
|
(
|
||||||
|
state_service,
|
||||||
|
read_state_service,
|
||||||
|
latest_chain_tip,
|
||||||
|
chain_tip_change,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,9 @@ use crate::{
|
||||||
HashOrHeight,
|
HashOrHeight,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
/// Returns the [`Block`] with [`block::Hash`](zebra_chain::block::Hash) or
|
/// Returns the [`Block`] with [`block::Hash`](zebra_chain::block::Hash) or
|
||||||
/// [`Height`](zebra_chain::block::Height),
|
/// [`Height`](zebra_chain::block::Height),
|
||||||
/// if it exists in the non-finalized `chain` or finalized `db`.
|
/// if it exists in the non-finalized `chain` or finalized `db`.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
//! Tests for the ReadStateService.
|
||||||
|
|
||||||
|
mod vectors;
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
//! Fixed test vectors for the ReadStateService.
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use zebra_chain::{
|
||||||
|
block::Block, parameters::Network::*, serialization::ZcashDeserializeInto, transaction,
|
||||||
|
};
|
||||||
|
|
||||||
|
use zebra_test::{
|
||||||
|
prelude::Result,
|
||||||
|
transcript::{ExpectedTranscriptError, Transcript},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{init_test_services, populated_state, ReadRequest, ReadResponse};
|
||||||
|
|
||||||
|
/// Test that ReadStateService responds correctly when empty.
|
||||||
|
#[tokio::test]
|
||||||
|
async fn empty_read_state_still_responds_to_requests() -> Result<()> {
|
||||||
|
zebra_test::init();
|
||||||
|
|
||||||
|
let transcript = Transcript::from(empty_state_test_cases());
|
||||||
|
|
||||||
|
let network = Mainnet;
|
||||||
|
let (_state, read_state, _latest_chain_tip, _chain_tip_change) = init_test_services(network);
|
||||||
|
|
||||||
|
transcript.check(read_state).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test that ReadStateService responds correctly when the state contains blocks.
|
||||||
|
#[tokio::test]
|
||||||
|
async fn populated_read_state_responds_correctly() -> Result<()> {
|
||||||
|
zebra_test::init();
|
||||||
|
|
||||||
|
// Create a continuous chain of mainnet blocks from genesis
|
||||||
|
let blocks: Vec<Arc<Block>> = zebra_test::vectors::CONTINUOUS_MAINNET_BLOCKS
|
||||||
|
.iter()
|
||||||
|
.map(|(_height, block_bytes)| block_bytes.zcash_deserialize_into().unwrap())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let (_state, read_state, _latest_chain_tip, _chain_tip_change) =
|
||||||
|
populated_state(blocks.clone(), Mainnet).await;
|
||||||
|
|
||||||
|
let empty_cases = Transcript::from(empty_state_test_cases());
|
||||||
|
empty_cases.check(read_state.clone()).await?;
|
||||||
|
|
||||||
|
for block in blocks {
|
||||||
|
let block_cases = vec![
|
||||||
|
(
|
||||||
|
ReadRequest::Block(block.hash().into()),
|
||||||
|
Ok(ReadResponse::Block(Some(block.clone()))),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ReadRequest::Block(block.coinbase_height().unwrap().into()),
|
||||||
|
Ok(ReadResponse::Block(Some(block.clone()))),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
let block_cases = Transcript::from(block_cases);
|
||||||
|
block_cases.check(read_state.clone()).await?;
|
||||||
|
|
||||||
|
// Spec: transactions in the genesis block are ignored.
|
||||||
|
if block.coinbase_height().unwrap().0 == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for transaction in &block.transactions {
|
||||||
|
let transaction_cases = vec![(
|
||||||
|
ReadRequest::Transaction(transaction.hash()),
|
||||||
|
Ok(ReadResponse::Transaction(Some(transaction.clone()))),
|
||||||
|
)];
|
||||||
|
|
||||||
|
let transaction_cases = Transcript::from(transaction_cases);
|
||||||
|
transaction_cases.check(read_state.clone()).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns test cases for the empty state and missing blocks.
|
||||||
|
fn empty_state_test_cases() -> Vec<(ReadRequest, Result<ReadResponse, ExpectedTranscriptError>)> {
|
||||||
|
let block: Arc<Block> = zebra_test::vectors::BLOCK_MAINNET_419200_BYTES
|
||||||
|
.zcash_deserialize_into()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
vec![
|
||||||
|
(
|
||||||
|
ReadRequest::Transaction(transaction::Hash([0; 32])),
|
||||||
|
Ok(ReadResponse::Transaction(None)),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ReadRequest::Block(block.hash().into()),
|
||||||
|
Ok(ReadResponse::Block(None)),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ReadRequest::Block(block.coinbase_height().unwrap().into()),
|
||||||
|
Ok(ReadResponse::Block(None)),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
|
//! StateService test vectors.
|
||||||
|
//!
|
||||||
|
//! TODO: move these tests into tests::vectors and tests::prop modules.
|
||||||
|
|
||||||
use std::{convert::TryInto, env, sync::Arc};
|
use std::{convert::TryInto, env, sync::Arc};
|
||||||
|
|
||||||
use tower::{buffer::Buffer, util::BoxService};
|
use tower::{buffer::Buffer, util::BoxService};
|
||||||
|
|
@ -11,6 +15,7 @@ use zebra_chain::{
|
||||||
transaction, transparent,
|
transaction, transparent,
|
||||||
value_balance::ValueBalance,
|
value_balance::ValueBalance,
|
||||||
};
|
};
|
||||||
|
|
||||||
use zebra_test::{prelude::*, transcript::Transcript};
|
use zebra_test::{prelude::*, transcript::Transcript};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
|
//! Basic integration tests for zebra-state
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use color_eyre::eyre::Report;
|
use color_eyre::eyre::Report;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::sync::Arc;
|
|
||||||
use zebra_chain::{block::Block, parameters::Network, serialization::ZcashDeserialize};
|
use zebra_chain::{block::Block, parameters::Network, serialization::ZcashDeserialize};
|
||||||
use zebra_test::transcript::{ExpectedTranscriptError, Transcript};
|
use zebra_test::transcript::{ExpectedTranscriptError, Transcript};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ impl ExpectedTranscriptError {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check the actual error `e` against this expected error.
|
/// Check the actual error `e` against this expected error.
|
||||||
|
#[track_caller]
|
||||||
fn check(&self, e: BoxError) -> Result<(), Report> {
|
fn check(&self, e: BoxError) -> Result<(), Report> {
|
||||||
match self {
|
match self {
|
||||||
ExpectedTranscriptError::Any => Ok(()),
|
ExpectedTranscriptError::Any => Ok(()),
|
||||||
|
|
@ -89,6 +90,7 @@ where
|
||||||
S: Debug + Eq,
|
S: Debug + Eq,
|
||||||
{
|
{
|
||||||
/// Check this transcript against the responses from the `to_check` service
|
/// Check this transcript against the responses from the `to_check` service
|
||||||
|
#[track_caller]
|
||||||
pub async fn check<C>(mut self, mut to_check: C) -> Result<(), Report>
|
pub async fn check<C>(mut self, mut to_check: C) -> Result<(), Report>
|
||||||
where
|
where
|
||||||
C: Service<R, Response = S>,
|
C: Service<R, Response = S>,
|
||||||
|
|
@ -169,6 +171,7 @@ where
|
||||||
Poll::Ready(Ok(()))
|
Poll::Ready(Ok(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
fn call(&mut self, request: R) -> Self::Future {
|
fn call(&mut self, request: R) -> Self::Future {
|
||||||
if let Some((expected_request, response)) = self.messages.next() {
|
if let Some((expected_request, response)) = self.messages.next() {
|
||||||
match response {
|
match response {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue