Skip to main content

fvm/kernel/
blocks.rs

1use std::collections::HashSet;
2// Copyright 2021-2023 Protocol Labs
3// SPDX-License-Identifier: Apache-2.0, MIT
4use std::rc::Rc;
5
6use cid::Cid;
7use fvm_ipld_encoding::ipld_block::IpldBlock;
8
9use super::Result;
10use crate::syscall_error;
11
12/// A registry of open blocks (per-kernel). Think "file descriptor" table. At the moment, there's no
13/// way to close/remove a block from this table.
14#[derive(Default)]
15pub struct BlockRegistry {
16    blocks: Vec<Block>,
17    reachable: HashSet<Cid>,
18}
19
20/// Blocks in the block registry are addressed by an ordinal, starting from 1 (`FIRST_ID`).
21/// The zero value is reserved to mean "no data", such as when actor invocations
22/// receive or return no data.
23pub type BlockId = u32;
24
25const FIRST_ID: BlockId = 1;
26const MAX_BLOCKS: u32 = i32::MAX as u32; // TODO(M2): Limit
27
28#[derive(Debug, Copy, Clone)]
29pub struct BlockStat {
30    pub codec: u64,
31    pub size: u32,
32}
33
34#[derive(Debug, Clone)]
35pub struct Block(Rc<BlockInner>);
36#[derive(Debug)]
37struct BlockInner {
38    codec: u64,
39    data: Box<[u8]>,
40    links: Box<[Cid]>,
41}
42
43impl Block {
44    pub fn new(codec: u64, data: impl Into<Box<[u8]>>, links: impl Into<Box<[Cid]>>) -> Self {
45        // This requires an extra allocation (ew) but no extra copy on send.
46        // The extra allocation is basically nothing.
47        Self(Rc::new(BlockInner {
48            codec,
49            data: data.into(),
50            links: links.into(),
51        }))
52    }
53
54    #[inline(always)]
55    pub fn codec(&self) -> u64 {
56        self.0.codec
57    }
58
59    #[inline(always)]
60    pub fn links(&self) -> &[Cid] {
61        &self.0.links
62    }
63
64    #[inline(always)]
65    pub fn data(&self) -> &[u8] {
66        &self.0.data
67    }
68
69    #[inline(always)]
70    pub fn size(&self) -> u32 {
71        self.0.data.len() as u32
72    }
73
74    #[inline(always)]
75    pub fn stat(&self) -> BlockStat {
76        BlockStat {
77            codec: self.codec(),
78            size: self.size(),
79        }
80    }
81}
82
83impl From<&Block> for IpldBlock {
84    fn from(b: &Block) -> Self {
85        IpldBlock {
86            codec: b.0.codec,
87            data: Vec::from(&*b.0.data),
88        }
89    }
90}
91
92impl BlockRegistry {
93    pub(crate) fn new() -> Self {
94        Self::default()
95    }
96}
97
98impl BlockRegistry {
99    /// Adds a new block to the registry, marking all children as reachable, returning a handle to
100    /// refer to it. Use this when adding a block known to be reachable.
101    pub fn put_reachable(&mut self, block: Block) -> Result<BlockId> {
102        self.put_inner(block, false)
103    }
104
105    /// Adds a new block to the registry, checking that all children are currently reachable,
106    /// returning a handle to refer to it. Use this when creating a _new_ block.
107    //
108    //  Returns a `NotFound` error if `block` references any unreachable CIDs.
109    pub fn put_check_reachable(&mut self, block: Block) -> Result<BlockId> {
110        self.put_inner(block, true)
111    }
112
113    /// Mark a cid as reachable. Call this when a new block is linked into the state.
114    pub fn mark_reachable(&mut self, k: &Cid) {
115        self.reachable.insert(*k);
116    }
117
118    /// Check if a block is reachable. Call this before attempting to read the block from the
119    /// datastore.
120    pub fn is_reachable(&self, k: &Cid) -> bool {
121        // NOTE: do not implicitly treat inline blocks (identity-hashed CIDs) as "reachable" as they
122        // may contain links to _unreachable_ children.
123        self.reachable.contains(k)
124    }
125
126    /// Adds a new block to the registry, and returns a handle to refer to it.
127    fn put_inner(&mut self, block: Block, check_reachable: bool) -> Result<BlockId> {
128        if self.is_full() {
129            return Err(syscall_error!(LimitExceeded; "too many blocks").into());
130        }
131
132        // We expect the caller to have already charged for gas.
133        if check_reachable {
134            if let Some(k) = block.links().iter().find(|k| !self.is_reachable(k)) {
135                return Err(syscall_error!(NotFound; "cannot put block: {k} not reachable").into());
136            }
137        } else {
138            for k in block.links() {
139                self.mark_reachable(k)
140            }
141        }
142
143        let id = FIRST_ID + self.blocks.len() as u32;
144        self.blocks.push(block);
145        Ok(id)
146    }
147
148    /// Gets the block associated with a block handle.
149    pub fn get(&self, id: BlockId) -> Result<&Block> {
150        if id < FIRST_ID {
151            return Err(syscall_error!(InvalidHandle; "invalid block handle {id}").into());
152        }
153        id.try_into()
154            .ok()
155            .and_then(|idx: usize| self.blocks.get(idx - FIRST_ID as usize))
156            .ok_or(syscall_error!(InvalidHandle; "invalid block handle {id}").into())
157    }
158
159    /// Returns the size & codec of the specified block.
160    pub fn stat(&self, id: BlockId) -> Result<BlockStat> {
161        if id < FIRST_ID {
162            return Err(syscall_error!(InvalidHandle; "invalid block handle {id}").into());
163        }
164        id.try_into()
165            .ok()
166            .and_then(|idx: usize| self.blocks.get(idx - FIRST_ID as usize))
167            .ok_or(syscall_error!(InvalidHandle; "invalid block handle {id}").into())
168            .map(|b| BlockStat {
169                codec: b.codec(),
170                size: b.size(),
171            })
172    }
173
174    pub fn is_full(&self) -> bool {
175        self.blocks.len() as u32 == MAX_BLOCKS
176    }
177}