use core::{ops::RangeInclusive, future::Future};
use alloc::{format, vec::Vec, string::ToString};
use monero_oxide::block::Block;
use crate::{InterfaceError, ProvidesBlockchainMeta};
pub trait ProvidesUnvalidatedBlockchain: Sync + ProvidesBlockchainMeta {
fn contiguous_blocks(
&self,
range: RangeInclusive<usize>,
) -> impl Send + Future<Output = Result<Vec<Block>, InterfaceError>> {
async move {
let mut blocks =
Vec::with_capacity(range.end().saturating_sub(*range.start()).saturating_add(1));
for number in range {
blocks.push(self.block_by_number(number).await?);
}
Ok(blocks)
}
}
fn block(&self, hash: [u8; 32]) -> impl Send + Future<Output = Result<Block, InterfaceError>>;
fn block_by_number(
&self,
number: usize,
) -> impl Send + Future<Output = Result<Block, InterfaceError>> {
async move {
let mut blocks = self.contiguous_blocks(number ..= number).await?;
if blocks.len() != 1 {
Err(InterfaceError::InternalError(format!(
"`{}` returned {} blocks, expected {}",
"ProvidesUnvalidatedBlockchain::contiguous_blocks",
blocks.len(),
1,
)))?;
}
Ok(blocks.pop().expect("verified we had a block"))
}
}
fn block_hash(
&self,
number: usize,
) -> impl Send + Future<Output = Result<[u8; 32], InterfaceError>>;
}
pub trait ProvidesBlockchain: ProvidesBlockchainMeta {
fn contiguous_blocks(
&self,
range: RangeInclusive<usize>,
) -> impl Send + Future<Output = Result<Vec<Block>, InterfaceError>>;
fn block(&self, hash: [u8; 32]) -> impl Send + Future<Output = Result<Block, InterfaceError>>;
fn block_by_number(
&self,
number: usize,
) -> impl Send + Future<Output = Result<Block, InterfaceError>>;
fn block_hash(
&self,
number: usize,
) -> impl Send + Future<Output = Result<[u8; 32], InterfaceError>>;
}
pub(crate) fn sanity_check_contiguous_blocks<'a>(
range: RangeInclusive<usize>,
blocks: impl Iterator<Item = &'a Block>,
) -> Result<(), InterfaceError> {
let mut parent = None;
for (number, block) in range.zip(blocks) {
if block.number() != number {
Err(InterfaceError::InvalidInterface(format!(
"requested block #{number}, received #{}",
block.number()
)))?;
}
let block_hash = block.hash();
if let Some(parent) = parent.or((number == 0).then_some([0; 32])) {
if parent != block.header.previous {
Err(InterfaceError::InvalidInterface(
"
interface returned a block which doesn't build on the prior block \
when requesting a contiguous series
"
.to_string(),
))?;
}
}
parent = Some(block_hash);
}
Ok(())
}
pub(crate) fn sanity_check_block_by_hash(
hash: &[u8; 32],
block: &Block,
) -> Result<(), InterfaceError> {
let actual_hash = block.hash();
if &actual_hash != hash {
Err(InterfaceError::InvalidInterface(format!(
"requested block {}, received {}",
hex::encode(hash),
hex::encode(actual_hash)
)))?;
}
Ok(())
}
pub(crate) fn sanity_check_block_by_number(
number: usize,
block: &Block,
) -> Result<(), InterfaceError> {
if block.number() != number {
Err(InterfaceError::InvalidInterface(format!(
"requested block #{number}, received #{}",
block.number()
)))?;
}
Ok(())
}
impl<P: ProvidesUnvalidatedBlockchain> ProvidesBlockchain for P {
fn contiguous_blocks(
&self,
range: RangeInclusive<usize>,
) -> impl Send + Future<Output = Result<Vec<Block>, InterfaceError>> {
async move {
let blocks =
<P as ProvidesUnvalidatedBlockchain>::contiguous_blocks(self, range.clone()).await?;
let expected_blocks =
range.end().saturating_sub(*range.start()).checked_add(1).ok_or_else(|| {
InterfaceError::InternalError(
"amount of blocks requested wasn't representable in a `usize`".to_string(),
)
})?;
if blocks.len() != expected_blocks {
Err(InterfaceError::InternalError(format!(
"`{}` returned {} blocks, expected {}",
"ProvidesUnvalidatedBlockchain::contiguous_blocks",
blocks.len(),
expected_blocks,
)))?;
}
sanity_check_contiguous_blocks(range, blocks.iter())?;
Ok(blocks)
}
}
fn block(&self, hash: [u8; 32]) -> impl Send + Future<Output = Result<Block, InterfaceError>> {
async move {
let block = <P as ProvidesUnvalidatedBlockchain>::block(self, hash).await?;
sanity_check_block_by_hash(&hash, &block)?;
Ok(block)
}
}
fn block_by_number(
&self,
number: usize,
) -> impl Send + Future<Output = Result<Block, InterfaceError>> {
async move {
let block = <P as ProvidesUnvalidatedBlockchain>::block_by_number(self, number).await?;
sanity_check_block_by_number(number, &block)?;
Ok(block)
}
}
fn block_hash(
&self,
number: usize,
) -> impl Send + Future<Output = Result<[u8; 32], InterfaceError>> {
<P as ProvidesUnvalidatedBlockchain>::block_hash(self, number)
}
}