use std::sync::Arc;
use futures::future::BoxFuture;
use tower::ServiceExt;
use tracing::Span;
use zakura_chain::{block, diagnostic::CodeTimer};
use super::{read, ReadStateService};
use crate::{request::TimedSpan, BoxError, ReadRequest};
#[derive(Debug)]
pub struct OwnedBlockRange<R> {
blocks: Vec<(block::Height, Arc<block::Block>, usize)>,
resources: R,
}
impl<R> OwnedBlockRange<R> {
pub fn into_parts(self) -> (Vec<(block::Height, Arc<block::Block>, usize)>, R) {
(self.blocks, self.resources)
}
}
impl ReadStateService {
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();
let best_chain = state.latest_best_chain();
spawn_owned_block_range(
start,
count,
max_response_bytes,
resources,
is_cancelled,
move |height| read::block_and_size(best_chain.clone(), &state.db, height.into()),
)
.await
}
}
fn spawn_owned_block_range<R: Send + 'static>(
start: block::Height,
count: u32,
max_response_bytes: u32,
resources: R,
mut is_cancelled: impl FnMut(&R) -> bool + Send + 'static,
mut get_block: impl FnMut(block::Height) -> Option<(Arc<block::Block>, usize)> + Send + 'static,
) -> BoxFuture<'static, Result<OwnedBlockRange<R>, BoxError>> {
let timed_span = TimedSpan::new(
CodeTimer::start_desc("blocks_by_height_range"),
Span::current(),
);
timed_span.spawn_blocking(move || {
let blocks = collect_bounded_height_range(start, count, max_response_bytes, |height| {
if is_cancelled(&resources) {
None
} else {
get_block(height)
}
});
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) -> Option<(T, usize)>,
) -> Vec<(block::Height, T, usize)> {
let mut response_bytes = 0u64;
(0..count)
.map_while(|offset| {
let height = start.0.checked_add(offset).map(block::Height)?;
let (block, size) = get_block(height)?;
let size_u64 = u64::try_from(size).ok()?;
let next_response_bytes = response_bytes.checked_add(size_u64)?;
if next_response_bytes > u64::from(max_response_bytes) {
return None;
}
response_bytes = next_response_bytes;
Some((height, block, size))
})
.collect()
}
#[cfg(test)]
mod tests;