use core::{default::Default, marker::PhantomData};
use std::fmt::{Display, Formatter};
use core_primitives::BlockNumber;
use crate::{
session::{SessionBoundaryInfo, SessionId},
sync::{
data::{BranchKnowledge, ResponseItem},
handler::Request,
Block, BlockStatus, ChainStatus, FinalizationStatus, Header, Justification,
UnverifiedHeader, UnverifiedHeaderFor, UnverifiedJustification,
},
BlockId,
};
#[derive(Debug, Clone)]
pub enum RequestHandlerError<T: Display> {
MissingBlock(BlockId),
MissingParent(BlockId),
RootMismatch,
LastBlockOfSessionNotJustified,
ChainStatusError(T),
}
impl<T: Display> From<T> for RequestHandlerError<T> {
fn from(value: T) -> Self {
RequestHandlerError::ChainStatusError(value)
}
}
impl<T: Display> Display for RequestHandlerError<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
RequestHandlerError::RootMismatch => write!(
f,
"invalid request, top_justification is not an ancestor of target"
),
RequestHandlerError::MissingParent(id) => write!(f, "missing parent of block {id:?}"),
RequestHandlerError::MissingBlock(id) => write!(f, "missing block {id:?}"),
RequestHandlerError::ChainStatusError(e) => write!(f, "{e}"),
RequestHandlerError::LastBlockOfSessionNotJustified => {
write!(f, "last block of finalized session not justified")
}
}
}
}
type Chunk<B, J> = Vec<ResponseItem<B, J>>;
pub trait HandlerTypes {
type Justification: Justification;
type ChainStatusError: Display;
}
type HandlerResult<T, HT> = Result<T, RequestHandlerError<<HT as HandlerTypes>::ChainStatusError>>;
#[derive(Debug)]
enum HeadOfChunk<J: Justification> {
Justification(J),
Header(J::Header),
}
impl<J: Justification> HeadOfChunk<J> {
pub fn id(&self) -> BlockId {
match self {
HeadOfChunk::Justification(j) => j.header().id(),
HeadOfChunk::Header(h) => h.id(),
}
}
pub fn parent_id(&self) -> Option<BlockId> {
match self {
HeadOfChunk::Justification(j) => j.header().parent_id(),
HeadOfChunk::Header(h) => h.parent_id(),
}
}
pub fn is_justification(&self) -> bool {
matches!(self, HeadOfChunk::Justification(_))
}
}
#[derive(PartialEq, Eq, Debug)]
enum State {
EverythingButHeader,
Everything,
OnlyJustification,
}
struct StepResult<B, J>
where
J: Justification,
B: Block<UnverifiedHeader = UnverifiedHeaderFor<J>>,
{
pre_chunk: PreChunk<B, J>,
state: State,
head: HeadOfChunk<J>,
}
impl<B, J> StepResult<B, J>
where
J: Justification,
B: Block<UnverifiedHeader = UnverifiedHeaderFor<J>>,
{
fn new(head: HeadOfChunk<J>, state: State) -> Self {
Self {
pre_chunk: PreChunk::new(&head),
state,
head,
}
}
pub fn current_id(&self) -> BlockId {
self.head.id()
}
pub fn update<CS: ChainStatus<B, J>>(
&mut self,
chain_status: &CS,
) -> Result<bool, RequestHandlerError<CS::Error>> {
match self.state {
State::EverythingButHeader => self.add_block(self.head.id(), chain_status)?,
State::Everything if self.head.is_justification() => {
self.add_block(self.head.id(), chain_status)?
}
State::Everything => self.add_block_and_header(self.head.id(), chain_status)?,
_ => {}
}
self.head = self.next_head(chain_status)?;
Ok(self.head.is_justification())
}
fn add_block<CS: ChainStatus<B, J>>(
&mut self,
id: BlockId,
chain_status: &CS,
) -> Result<(), RequestHandlerError<CS::Error>> {
let block = chain_status
.block(id.clone())?
.ok_or(RequestHandlerError::MissingBlock(id))?;
self.pre_chunk.add_block(block);
Ok(())
}
fn add_block_and_header<CS: ChainStatus<B, J>>(
&mut self,
id: BlockId,
chain_status: &CS,
) -> Result<(), RequestHandlerError<CS::Error>> {
let block = chain_status
.block(id.clone())?
.ok_or(RequestHandlerError::MissingBlock(id))?;
self.pre_chunk.add_block_and_header(block);
Ok(())
}
fn next_head<CS: ChainStatus<B, J>>(
&self,
chain_status: &CS,
) -> Result<HeadOfChunk<J>, RequestHandlerError<CS::Error>> {
let parent_id = self
.head
.parent_id()
.ok_or(RequestHandlerError::MissingParent(self.head.id()))?;
let head = match chain_status.status_of(parent_id.clone())? {
BlockStatus::Justified(j) => HeadOfChunk::Justification(j),
BlockStatus::Present(h) => HeadOfChunk::Header(h),
BlockStatus::Unknown => return Err(RequestHandlerError::MissingBlock(parent_id)),
};
Ok(head)
}
pub fn start_sending_headers(&mut self) {
self.state = State::Everything;
}
pub fn stop_sending_blocks(&mut self) {
self.state = State::OnlyJustification;
}
pub fn finish(self) -> (Chunk<B, J>, State, HeadOfChunk<J>) {
let chunk = self.pre_chunk.into_chunk();
(chunk, self.state, self.head)
}
}
#[derive(Debug)]
pub enum Action<B, J>
where
J: Justification,
B: Block<UnverifiedHeader = UnverifiedHeaderFor<J>>,
{
RequestBlock(BlockId),
Response(Vec<ResponseItem<B, J>>),
Noop,
}
impl<B, J> Action<B, J>
where
J: Justification,
B: Block<UnverifiedHeader = UnverifiedHeaderFor<J>>,
{
fn request_block(id: BlockId) -> Self {
Action::RequestBlock(id)
}
fn new(response_items: Vec<ResponseItem<B, J>>) -> Self {
match response_items.is_empty() {
true => Action::Noop,
false => Action::Response(response_items),
}
}
}
#[derive(Default)]
struct PreChunk<B, J>
where
J: Justification,
B: Block<UnverifiedHeader = UnverifiedHeaderFor<J>>,
{
pub just: Option<J>,
pub blocks: Vec<B>,
pub headers: Vec<UnverifiedHeaderFor<J>>,
}
impl<B, J> PreChunk<B, J>
where
J: Justification,
B: Block<UnverifiedHeader = UnverifiedHeaderFor<J>>,
{
fn new(head: &HeadOfChunk<J>) -> Self {
match head {
HeadOfChunk::Justification(j) => Self::from_just(j.clone()),
HeadOfChunk::Header(_) => Self::empty(),
}
}
fn empty() -> Self {
Self {
just: None,
blocks: vec![],
headers: vec![],
}
}
fn single_block(block: B) -> Self {
let mut result = Self::empty();
result.add_block_and_header(block);
result
}
fn from_just(justification: J) -> Self {
Self {
just: Some(justification),
blocks: vec![],
headers: vec![],
}
}
fn into_chunk(mut self) -> Chunk<B, J> {
let mut chunks = vec![];
if let Some(j) = self.just {
chunks.push(ResponseItem::Justification(j.into_unverified()));
}
chunks.extend(self.headers.into_iter().map(ResponseItem::Header));
self.blocks.reverse();
chunks.extend(self.blocks.into_iter().map(ResponseItem::Block));
chunks
}
pub fn add_block(&mut self, b: B) {
self.blocks.push(b);
}
pub fn add_block_and_header(&mut self, b: B) {
self.headers.push(b.header().clone());
self.blocks.push(b);
}
}
pub struct RequestHandler<'a, B, J, CS>
where
J: Justification,
B: Block<UnverifiedHeader = UnverifiedHeaderFor<J>>,
CS: ChainStatus<B, J>,
{
chain_status: &'a CS,
session_info: &'a SessionBoundaryInfo,
_phantom: PhantomData<(B, J)>,
}
impl<'a, B, J, CS> HandlerTypes for RequestHandler<'a, B, J, CS>
where
J: Justification,
B: Block<UnverifiedHeader = UnverifiedHeaderFor<J>>,
CS: ChainStatus<B, J>,
{
type Justification = J;
type ChainStatusError = CS::Error;
}
impl<'a, B, J, CS> RequestHandler<'a, B, J, CS>
where
J: Justification,
B: Block<UnverifiedHeader = UnverifiedHeaderFor<J>>,
CS: ChainStatus<B, J>,
{
pub fn new(chain_status: &'a CS, session_info: &'a SessionBoundaryInfo) -> Self {
Self {
chain_status,
session_info,
_phantom: PhantomData,
}
}
fn upper_limit(&self, id: BlockId) -> BlockNumber {
let session = self.session_info.session_id_from_block_num(id.number());
self.session_info
.last_block_of_session(SessionId(session.0 + 1))
}
fn is_result_complete(
&self,
result: &mut StepResult<B, J>,
branch_knowledge: &BranchKnowledge,
to: &BlockId,
) -> HandlerResult<bool, Self> {
Ok(match branch_knowledge {
_ if result.current_id() == *to => true,
_ if result.current_id().number() <= to.number() => {
return Err(RequestHandlerError::RootMismatch);
}
BranchKnowledge::LowestId(id) if *id == result.current_id() => {
result.start_sending_headers();
result.update(self.chain_status)?
}
BranchKnowledge::TopImported(id) if *id == result.current_id() => {
result.stop_sending_blocks();
result.update(self.chain_status)?
}
_ => result.update(self.chain_status)?,
})
}
fn step(
&self,
state: State,
from: HeadOfChunk<J>,
to: &BlockId,
branch_knowledge: &BranchKnowledge,
) -> HandlerResult<Option<StepResult<B, J>>, Self> {
if from.id() == *to {
return Ok(None);
}
let mut result = StepResult::new(from, state);
while !self.is_result_complete(&mut result, branch_knowledge, to)? {}
Ok(Some(result))
}
fn response_items(
self,
mut head: HeadOfChunk<J>,
branch_knowledge: BranchKnowledge,
to: BlockId,
) -> HandlerResult<Vec<ResponseItem<B, J>>, Self> {
let mut response_items = vec![];
let mut state = State::EverythingButHeader;
while let Some(result) = self.step(state, head, &to, &branch_knowledge)? {
let (chunk, new_state, new_head) = result.finish();
state = new_state;
head = new_head;
response_items.push(chunk);
}
response_items.reverse();
Ok(response_items.into_iter().flatten().collect())
}
fn adjust_head(
&self,
head: HeadOfChunk<J>,
our_top_justification: J,
upper_limit: BlockNumber,
) -> HandlerResult<HeadOfChunk<J>, Self> {
let target = head.id();
let our_top_justification_number = our_top_justification.header().id().number();
Ok(if target.number() > our_top_justification_number {
head
} else if upper_limit > our_top_justification_number {
HeadOfChunk::Justification(our_top_justification)
} else {
match self.chain_status.finalized_at(upper_limit)? {
FinalizationStatus::FinalizedWithJustification(j) => HeadOfChunk::Justification(j),
_ => return Err(RequestHandlerError::LastBlockOfSessionNotJustified),
}
})
}
pub fn action(self, request: Request<J>) -> HandlerResult<Action<B, J>, Self> {
let our_top_justification = self.chain_status.top_finalized()?;
let top_justification = request.state().top_justification();
let target = request.target_id();
let upper_limit = self.upper_limit(top_justification.header().id());
if target.number() > upper_limit {
return Ok(Action::Noop);
}
let head = match self.chain_status.status_of(target.clone())? {
BlockStatus::Unknown => return Ok(Action::request_block(target.clone())),
BlockStatus::Justified(justification) => HeadOfChunk::Justification(justification),
BlockStatus::Present(header) => HeadOfChunk::Header(header),
};
let head = self.adjust_head(head, our_top_justification, upper_limit)?;
let response_items = self.response_items(
head,
request.branch_knowledge().clone(),
top_justification.header().id(),
)?;
Ok(Action::new(response_items))
}
}
pub fn block_to_response<J: Justification, B: Block<UnverifiedHeader = UnverifiedHeaderFor<J>>>(
block: B,
) -> Vec<ResponseItem<B, J>> {
PreChunk::single_block(block).into_chunk()
}