Skip to content

Commit

Permalink
5. change(state): split ReadStateService requests into a ReadRequest …
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
teor2345 authored Mar 17, 2022
1 parent b9640fb commit 39dfca8
Show file tree
Hide file tree
Showing 13 changed files with 233 additions and 92 deletions.
19 changes: 10 additions & 9 deletions zebra-rpc/src/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ pub struct RpcImpl<Mempool, State, Tip>
where
Mempool: Service<mempool::Request, Response = mempool::Response, Error = BoxError>,
State: Service<
zebra_state::Request,
Response = zebra_state::Response,
zebra_state::ReadRequest,
Response = zebra_state::ReadResponse,
Error = zebra_state::BoxError,
>,
Tip: ChainTip,
Expand All @@ -137,8 +137,8 @@ impl<Mempool, State, Tip> RpcImpl<Mempool, State, Tip>
where
Mempool: Service<mempool::Request, Response = mempool::Response, Error = BoxError>,
State: Service<
zebra_state::Request,
Response = zebra_state::Response,
zebra_state::ReadRequest,
Response = zebra_state::ReadResponse,
Error = zebra_state::BoxError,
>,
Tip: ChainTip + Send + Sync,
Expand Down Expand Up @@ -170,8 +170,8 @@ where
tower::Service<mempool::Request, Response = mempool::Response, Error = BoxError> + 'static,
Mempool::Future: Send,
State: Service<
zebra_state::Request,
Response = zebra_state::Response,
zebra_state::ReadRequest,
Response = zebra_state::ReadResponse,
Error = zebra_state::BoxError,
> + Clone
+ Send
Expand Down Expand Up @@ -257,7 +257,8 @@ where
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
.ready()
.and_then(|service| service.call(request))
Expand All @@ -269,8 +270,8 @@ where
})?;

match response {
zebra_state::Response::Block(Some(block)) => Ok(GetBlock(block.into())),
zebra_state::Response::Block(None) => Err(Error {
zebra_state::ReadResponse::Block(Some(block)) => Ok(GetBlock(block.into())),
zebra_state::ReadResponse::Block(None) => Err(Error {
code: ErrorCode::ServerError(0),
message: "Block not found".to_string(),
data: None,
Expand Down
23 changes: 9 additions & 14 deletions zebra-rpc/src/methods/tests/vectors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ use zebra_test::mock_service::MockService;

use super::super::*;

// Number of blocks to populate state with
const NUMBER_OF_BLOCKS: u32 = 10;

#[tokio::test]
async fn rpc_getinfo() {
zebra_test::init();
Expand Down Expand Up @@ -51,10 +48,10 @@ async fn rpc_getinfo() {
async fn rpc_getblock() {
zebra_test::init();

// Put the first `NUMBER_OF_BLOCKS` blocks in a vector
let blocks: Vec<Arc<Block>> = zebra_test::vectors::MAINNET_BLOCKS
.range(0..=NUMBER_OF_BLOCKS)
.map(|(_, block_bytes)| block_bytes.zcash_deserialize_into::<Arc<Block>>().unwrap())
// 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 mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
Expand Down Expand Up @@ -114,17 +111,15 @@ async fn rpc_getblock_error() {
async fn rpc_getbestblockhash() {
zebra_test::init();

// Put `NUMBER_OF_BLOCKS` blocks in a vector
let blocks: Vec<Arc<Block>> = zebra_test::vectors::MAINNET_BLOCKS
.range(0..=NUMBER_OF_BLOCKS)
.map(|(_, block_bytes)| block_bytes.zcash_deserialize_into::<Arc<Block>>().unwrap())
// 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();

// 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
let tip_block: Block = zebra_test::vectors::BLOCK_MAINNET_10_BYTES
.zcash_deserialize_into()
.unwrap();
let tip_block = blocks.last().unwrap();
let tip_block_hash = tip_block.hash();

// Get a mempool handle
Expand Down
4 changes: 2 additions & 2 deletions zebra-rpc/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ impl RpcServer {
+ 'static,
Mempool::Future: Send,
State: Service<
zebra_state::Request,
Response = zebra_state::Response,
zebra_state::ReadRequest,
Response = zebra_state::ReadResponse,
Error = zebra_state::BoxError,
> + Clone
+ Send
Expand Down
6 changes: 3 additions & 3 deletions zebra-state/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ mod tests;
pub use config::Config;
pub use constants::MAX_BLOCK_REORG_HEIGHT;
pub use error::{BoxError, CloneError, CommitBlockError, ValidateContextError};
pub use request::{FinalizedBlock, HashOrHeight, PreparedBlock, Request};
pub use response::Response;
pub use request::{FinalizedBlock, HashOrHeight, PreparedBlock, ReadRequest, Request};
pub use response::{ReadResponse, Response};
pub use service::{
chain_tip::{ChainTipChange, LatestChainTip, TipAction},
init,
Expand All @@ -42,7 +42,7 @@ pub use service::{
pub use service::{
arbitrary::populated_state,
chain_tip::{ChainTipBlock, ChainTipSender},
init_test,
init_test, init_test_services,
};

pub(crate) use request::ContextuallyValidBlock;
27 changes: 26 additions & 1 deletion zebra-state/src/request.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! State [`tower::Service`] request types.
use std::{collections::HashMap, sync::Arc};

use zebra_chain::{
Expand Down Expand Up @@ -229,7 +231,7 @@ impl From<ContextuallyValidBlock> for FinalizedBlock {
}

#[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 {
/// Performs contextual validation of the given block, committing it to the
/// state if successful.
Expand Down Expand Up @@ -377,3 +379,26 @@ pub enum Request {
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),
}
15 changes: 14 additions & 1 deletion zebra-state/src/response.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
//! State [`tower::Service`] response types.
use std::sync::Arc;

use zebra_chain::{
block::{self, Block},
transaction::Transaction,
Expand All @@ -11,7 +14,7 @@ use zebra_chain::{
use crate::Request;

#[derive(Clone, Debug, PartialEq, Eq)]
/// A response to a state [`Request`].
/// A response to a [`StateService`] [`Request`].
pub enum Response {
/// Response to [`Request::CommitBlock`] indicating that a block was
/// successfully committed to the state.
Expand Down Expand Up @@ -41,3 +44,13 @@ pub enum Response {
/// The response to a `FindBlockHeaders` request.
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>>),
}
109 changes: 48 additions & 61 deletions zebra-state/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ use crate::{
pending_utxos::PendingUtxos,
watch_receiver::WatchReceiver,
},
BoxError, CloneError, CommitBlockError, Config, FinalizedBlock, PreparedBlock, Request,
Response, ValidateContextError,
BoxError, CloneError, CommitBlockError, Config, FinalizedBlock, PreparedBlock, ReadRequest,
ReadResponse, Request, Response, ValidateContextError,
};

pub mod block_iter;
Expand Down Expand Up @@ -902,8 +902,8 @@ impl Service<Request> for StateService {
}
}

impl Service<Request> for ReadStateService {
type Response = Response;
impl Service<ReadRequest> for ReadStateService {
type Response = ReadResponse;
type Error = BoxError;
type Future =
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
Expand All @@ -913,10 +913,19 @@ impl Service<Request> for ReadStateService {
}

#[instrument(name = "read_state", skip(self))]
fn call(&mut self, req: Request) -> Self::Future {
fn call(&mut self, req: ReadRequest) -> Self::Future {
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.
Request::Block(hash_or_height) => {
ReadRequest::Block(hash_or_height) => {
metrics::counter!(
"state.requests",
1,
Expand All @@ -931,13 +940,13 @@ impl Service<Request> for ReadStateService {
read::block(best_chain, &state.db, hash_or_height)
});

Ok(Response::Block(block))
Ok(ReadResponse::Block(block))
}
.boxed()
}

// For the get_raw_transaction RPC, to be implemented in #3145.
Request::Transaction(hash) => {
ReadRequest::Transaction(hash) => {
metrics::counter!(
"state.requests",
1,
Expand All @@ -952,60 +961,10 @@ impl Service<Request> for ReadStateService {
read::transaction(best_chain, &state.db, hash)
});

Ok(Response::Transaction(transaction))
Ok(ReadResponse::Transaction(transaction))
}
.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")
}
}
}
}
Expand Down Expand Up @@ -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"))]
pub fn init_test(network: Network) -> Buffer<BoxService<Request, Response, BoxError>, Request> {
let (state_service, _, _, _) = StateService::new(Config::ephemeral(), network);

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,
)
}
3 changes: 3 additions & 0 deletions zebra-state/src/service/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ use crate::{
HashOrHeight,
};

#[cfg(test)]
mod tests;

/// Returns the [`Block`] with [`block::Hash`](zebra_chain::block::Hash) or
/// [`Height`](zebra_chain::block::Height),
/// if it exists in the non-finalized `chain` or finalized `db`.
Expand Down
3 changes: 3 additions & 0 deletions zebra-state/src/service/read/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
//! Tests for the ReadStateService.
mod vectors;
Loading

0 comments on commit 39dfca8

Please sign in to comment.