use std::sync::Arc;
use futures::future::BoxFuture;
use tower::ServiceExt;
use tracing::Span;
use zakura_chain::{block, diagnostic::CodeTimer, serialization::ZcashSerialize};
use super::{finalized_state::ZakuraDb, non_finalized_state::Chain, read, ReadStateService};
use crate::{request::TimedSpan, BoxError, ReadRequest};
pub(super) struct BlockRangeSnapshot {
chain: Option<Arc<Chain>>,
tip: Option<block::Height>,
}
impl BlockRangeSnapshot {
pub(super) fn block_and_size(
&self,
db: &ZakuraDb,
height: block::Height,
) -> Option<(Arc<block::Block>, usize)> {
if height > self.tip? {
return None;
}
read::block_and_size(self.chain.clone(), db, height.into())
}
pub(super) fn serialized_block_within(
&self,
db: &ZakuraDb,
height: block::Height,
max_bytes: usize,
) -> Option<Vec<u8>> {
if height > self.tip? {
return None;
}
if let Some(chain) = self.chain.as_ref() {
if let Some(contextual) = chain.block(height.into()) {
if chain.block_info(height.into()).is_some_and(|info| {
usize::try_from(info.size()).is_ok_and(|size| size > max_bytes)
}) {
return None;
}
let bytes = contextual
.block
.zcash_serialize_to_vec()
.expect("a committed block serializes because writing to a Vec is infallible");
return (bytes.len() <= max_bytes).then_some(bytes);
}
}
db.raw_block_bytes_within(height.into(), max_bytes)
}
}
#[derive(Debug)]
pub struct OwnedBlockRange<R> {
blocks: Vec<(block::Height, Vec<u8>)>,
resources: R,
}
impl<R> OwnedBlockRange<R> {
pub fn into_parts(self) -> (Vec<(block::Height, Vec<u8>)>, R) {
(self.blocks, self.resources)
}
}
impl ReadStateService {
pub(super) fn block_range_snapshot(&self) -> Result<BlockRangeSnapshot, BoxError> {
let reader = self.header_chain_reader_receiver.borrow().clone();
let Some(reader) = reader else {
let chain = self.latest_best_chain();
return Ok(BlockRangeSnapshot {
tip: read::tip_height(chain.clone(), &self.db),
chain,
});
};
let (non_finalized, _, selected) = reader.with_selected_overlap(
|| self.latest_non_finalized_state(),
|state| {
state
.chain_iter()
.map(|chain| chain.non_finalized_tip().0)
.max()
.or_else(|| self.db.finalized_tip_height())
},
)?;
for frontier in selected.iter().rev() {
if let Some(chain) =
non_finalized.find_chain(|chain| chain.contains_block_hash(frontier.hash))
{
return Ok(BlockRangeSnapshot {
chain: Some(chain),
tip: Some(frontier.height),
});
}
}
let finalized = selected
.first()
.expect("the coherent full-state overlap includes the finalized header");
Ok(BlockRangeSnapshot {
chain: None,
tip: Some(finalized.height),
})
}
pub async fn read_owned_block_range<R: Send + 'static>(
&mut self,
start: block::Height,
count: u32,
max_response_bytes: u32,
resources: R,
is_cancelled: impl FnMut(&R) -> bool + Send + 'static,
) -> Result<OwnedBlockRange<R>, BoxError> {
self.ready().await?;
ReadRequest::BlocksByHeightRange { start, count }.count_metric();
let state = self.clone();
spawn_owned_block_range(
start,
count,
max_response_bytes,
resources,
is_cancelled,
move || {
let snapshot = state.block_range_snapshot()?;
Ok(move |height, remaining| {
snapshot.serialized_block_within(&state.db, height, remaining)
})
},
)
.await
}
}
fn spawn_owned_block_range<R, G>(
start: block::Height,
count: u32,
max_response_bytes: u32,
resources: R,
mut is_cancelled: impl FnMut(&R) -> bool + Send + 'static,
prepare_read: impl FnOnce() -> Result<G, BoxError> + Send + 'static,
) -> BoxFuture<'static, Result<OwnedBlockRange<R>, BoxError>>
where
R: Send + 'static,
G: FnMut(block::Height, usize) -> Option<Vec<u8>>,
{
let timed_span = TimedSpan::new(
CodeTimer::start_desc("blocks_by_height_range"),
Span::current(),
);
timed_span.spawn_blocking(move || {
if is_cancelled(&resources) {
return Ok(OwnedBlockRange {
blocks: Vec::new(),
resources,
});
}
let mut get_block = prepare_read()?;
let blocks =
collect_bounded_height_range(start, count, max_response_bytes, |height, remaining| {
if is_cancelled(&resources) {
return None;
}
let bytes = get_block(height, remaining)?;
let size = bytes.len();
Some((bytes, size))
})
.into_iter()
.map(|(height, bytes, _)| (height, bytes))
.collect();
Ok(OwnedBlockRange { blocks, resources })
})
}
fn collect_bounded_height_range<T>(
start: block::Height,
count: u32,
max_response_bytes: u32,
mut get_block: impl FnMut(block::Height, usize) -> Option<(T, usize)>,
) -> Vec<(block::Height, T, usize)> {
let mut remaining =
usize::try_from(max_response_bytes).expect("u32 fits usize on supported targets");
(0..count)
.map_while(|offset| {
let height = start.0.checked_add(offset).map(block::Height)?;
let (block, size) = get_block(height, remaining)?;
remaining = remaining.checked_sub(size)?;
Some((height, block, size))
})
.collect()
}
#[cfg(test)]
mod storage_probe;
#[cfg(test)]
mod tests;