use std::collections::HashSet;
use std::rc::Rc;
use cid::Cid;
use fvm_ipld_encoding::ipld_block::IpldBlock;
use super::Result;
use crate::syscall_error;
#[derive(Default)]
pub struct BlockRegistry {
blocks: Vec<Block>,
reachable: HashSet<Cid>,
}
pub type BlockId = u32;
const FIRST_ID: BlockId = 1;
const MAX_BLOCKS: u32 = i32::MAX as u32;
#[derive(Debug, Copy, Clone)]
pub struct BlockStat {
pub codec: u64,
pub size: u32,
}
#[derive(Debug, Clone)]
pub struct Block(Rc<BlockInner>);
#[derive(Debug)]
struct BlockInner {
codec: u64,
data: Box<[u8]>,
links: Box<[Cid]>,
}
impl Block {
pub fn new(codec: u64, data: impl Into<Box<[u8]>>, links: impl Into<Box<[Cid]>>) -> Self {
Self(Rc::new(BlockInner {
codec,
data: data.into(),
links: links.into(),
}))
}
#[inline(always)]
pub fn codec(&self) -> u64 {
self.0.codec
}
#[inline(always)]
pub fn links(&self) -> &[Cid] {
&self.0.links
}
#[inline(always)]
pub fn data(&self) -> &[u8] {
&self.0.data
}
#[inline(always)]
pub fn size(&self) -> u32 {
self.0.data.len() as u32
}
#[inline(always)]
pub fn stat(&self) -> BlockStat {
BlockStat {
codec: self.codec(),
size: self.size(),
}
}
}
impl From<&Block> for IpldBlock {
fn from(b: &Block) -> Self {
IpldBlock {
codec: b.0.codec,
data: Vec::from(&*b.0.data),
}
}
}
impl BlockRegistry {
pub(crate) fn new() -> Self {
Self::default()
}
}
impl BlockRegistry {
pub fn put_reachable(&mut self, block: Block) -> Result<BlockId> {
self.put_inner(block, false)
}
pub fn put_check_reachable(&mut self, block: Block) -> Result<BlockId> {
self.put_inner(block, true)
}
pub fn mark_reachable(&mut self, k: &Cid) {
self.reachable.insert(*k);
}
pub fn is_reachable(&self, k: &Cid) -> bool {
self.reachable.contains(k)
}
fn put_inner(&mut self, block: Block, check_reachable: bool) -> Result<BlockId> {
if self.is_full() {
return Err(syscall_error!(LimitExceeded; "too many blocks").into());
}
if check_reachable {
if let Some(k) = block.links().iter().find(|k| !self.is_reachable(k)) {
return Err(syscall_error!(NotFound; "cannot put block: {k} not reachable").into());
}
} else {
for k in block.links() {
self.mark_reachable(k)
}
}
let id = FIRST_ID + self.blocks.len() as u32;
self.blocks.push(block);
Ok(id)
}
pub fn get(&self, id: BlockId) -> Result<&Block> {
if id < FIRST_ID {
return Err(syscall_error!(InvalidHandle; "invalid block handle {id}").into());
}
id.try_into()
.ok()
.and_then(|idx: usize| self.blocks.get(idx - FIRST_ID as usize))
.ok_or(syscall_error!(InvalidHandle; "invalid block handle {id}").into())
}
pub fn stat(&self, id: BlockId) -> Result<BlockStat> {
if id < FIRST_ID {
return Err(syscall_error!(InvalidHandle; "invalid block handle {id}").into());
}
id.try_into()
.ok()
.and_then(|idx: usize| self.blocks.get(idx - FIRST_ID as usize))
.ok_or(syscall_error!(InvalidHandle; "invalid block handle {id}").into())
.map(|b| BlockStat {
codec: b.codec(),
size: b.size(),
})
}
pub fn is_full(&self) -> bool {
self.blocks.len() as u32 == MAX_BLOCKS
}
}