tycho_types/cell/
cell_context.rs

1use sha2::digest::Digest;
2
3use crate::cell::{Cell, CellDescriptor, CellType, DynCell, HashBytes, LevelMask, MAX_REF_COUNT};
4use crate::error::Error;
5use crate::util::{ArrayVec, unlikely};
6
7/// Gas accounting and resolcing exotic cells.
8pub trait CellContext {
9    /// Builds a new cell from cell parts.
10    fn finalize_cell(&self, cell: CellParts<'_>) -> Result<Cell, Error>;
11
12    /// Resolve an owned cell.
13    fn load_cell(&self, cell: Cell, mode: LoadMode) -> Result<Cell, Error>;
14
15    /// Resolve a cell reference.
16    fn load_dyn_cell<'s: 'a, 'a>(
17        &'s self,
18        cell: &'a DynCell,
19        mode: LoadMode,
20    ) -> Result<&'a DynCell, Error>;
21}
22
23/// Dictionary insertion mode.
24#[derive(Debug, Clone, Copy, Eq, PartialEq)]
25#[repr(u8)]
26pub enum LoadMode {
27    /// Do not modify the default behavior.
28    Noop = 0b00,
29    /// Count the cost of loading the cell.
30    UseGas = 0b01,
31    /// Resolve exotic cells such as libraries or merkle stuff.
32    Resolve = 0b10,
33    /// Both `UseGas` and `Resolve`.
34    Full = 0b11,
35}
36
37impl LoadMode {
38    /// Returns `true` if this mode requires gas accounting.
39    #[inline]
40    pub const fn use_gas(self) -> bool {
41        self as u8 & 0b01 != 0
42    }
43
44    /// Returns `true` if exotic cells are resolved in this mode.
45    #[inline]
46    pub const fn resolve(self) -> bool {
47        self as u8 & 0b10 != 0
48    }
49}
50
51/// Partially assembled cell.
52pub struct CellParts<'a> {
53    /// Length of this cell's data in bits.
54    pub bit_len: u16,
55
56    /// Well-formed cell descriptor.
57    pub descriptor: CellDescriptor,
58
59    /// Bitwise OR of child level masks.
60    pub children_mask: LevelMask,
61
62    /// Array of child cells.
63    ///
64    /// NOTE: it is guaranteed that the length of the array is consistent
65    /// with the descriptor.
66    pub references: ArrayVec<Cell, MAX_REF_COUNT>,
67
68    /// Cell data slice.
69    pub data: &'a [u8],
70}
71
72impl CellParts<'_> {
73    /// Validates cell and computes all hashes.
74    pub fn compute_hashes(&self) -> Result<Box<[(HashBytes, u16)]>, Error> {
75        const HASH_BITS: usize = 256;
76        const DEPTH_BITS: usize = 16;
77
78        let mut descriptor = self.descriptor;
79        let bit_len = self.bit_len as usize;
80        let level_mask = descriptor.level_mask();
81        let level = level_mask.level() as usize;
82
83        let references = self.references.as_ref();
84
85        // `hashes_len` is guaranteed to be in range 1..4
86        let mut hashes_len = level + 1;
87
88        let (cell_type, computed_level_mask) = if unlikely(descriptor.is_exotic()) {
89            let Some(&first_byte) = self.data.first() else {
90                return Err(Error::InvalidCell);
91            };
92
93            match CellType::from_byte_exotic(first_byte) {
94                // 8 bits type, 8 bits level mask, level x (hash, depth)
95                Some(CellType::PrunedBranch) => {
96                    if unlikely(level == 0) {
97                        return Err(Error::InvalidCell);
98                    }
99
100                    let expected_bit_len = 8 + 8 + level * (HASH_BITS + DEPTH_BITS);
101                    if unlikely(bit_len != expected_bit_len || !references.is_empty()) {
102                        return Err(Error::InvalidCell);
103                    }
104
105                    let stored_mask = self.data.get(1).copied().unwrap_or_default();
106                    if unlikely(level_mask != stored_mask) {
107                        return Err(Error::InvalidCell);
108                    }
109
110                    hashes_len = 1;
111                    (CellType::PrunedBranch, level_mask)
112                }
113                // 8 bits type, hash, depth
114                Some(CellType::MerkleProof) => {
115                    const EXPECTED_BIT_LEN: usize = 8 + HASH_BITS + DEPTH_BITS;
116                    if unlikely(bit_len != EXPECTED_BIT_LEN || references.len() != 1) {
117                        return Err(Error::InvalidCell);
118                    }
119
120                    (CellType::MerkleProof, self.children_mask.virtualize(1))
121                }
122                // 8 bits type, 2 x (hash, depth)
123                Some(CellType::MerkleUpdate) => {
124                    const EXPECTED_BIT_LEN: usize = 8 + 2 * (HASH_BITS + DEPTH_BITS);
125                    if unlikely(bit_len != EXPECTED_BIT_LEN || references.len() != 2) {
126                        return Err(Error::InvalidCell);
127                    }
128
129                    (CellType::MerkleUpdate, self.children_mask.virtualize(1))
130                }
131                // 8 bits type, hash
132                Some(CellType::LibraryReference) => {
133                    const EXPECTED_BIT_LEN: usize = 8 + HASH_BITS;
134                    if unlikely(bit_len != EXPECTED_BIT_LEN || !references.is_empty()) {
135                        return Err(Error::InvalidCell);
136                    }
137
138                    (CellType::LibraryReference, LevelMask::EMPTY)
139                }
140                _ => return Err(Error::InvalidCell),
141            }
142        } else {
143            (CellType::Ordinary, self.children_mask)
144        };
145
146        if unlikely(computed_level_mask != level_mask) {
147            return Err(Error::InvalidCell);
148        }
149
150        let level_offset = cell_type.is_merkle() as u8;
151        let is_pruned = cell_type.is_pruned_branch();
152
153        let mut hashes = Vec::<(HashBytes, u16)>::with_capacity(hashes_len);
154        for level in 0..4 {
155            // Skip non-zero levels for pruned branches and insignificant hashes for other cells
156            if level != 0 && (is_pruned || !level_mask.contains(level)) {
157                continue;
158            }
159
160            let mut hasher = sha2::Sha256::new();
161
162            let level_mask = if is_pruned {
163                level_mask
164            } else {
165                LevelMask::from_level(level)
166            };
167
168            descriptor.d1 &= !(CellDescriptor::LEVEL_MASK | CellDescriptor::STORE_HASHES_MASK);
169            descriptor.d1 |= u8::from(level_mask) << 5;
170            hasher.update([descriptor.d1, descriptor.d2]);
171
172            if level == 0 {
173                hasher.update(self.data);
174            } else {
175                // SAFETY: new hash is added on each iteration, so there will
176                // definitely be a hash, when level>0
177                let prev_hash = unsafe { hashes.last().unwrap_unchecked() };
178                hasher.update(prev_hash.0.as_slice());
179            }
180
181            let mut depth = 0;
182            for child in references {
183                let child_depth = child.as_ref().depth(level + level_offset);
184                let next_depth = match child_depth.checked_add(1) {
185                    Some(next_depth) => next_depth,
186                    None => return Err(Error::DepthOverflow),
187                };
188                depth = std::cmp::max(depth, next_depth);
189
190                hasher.update(child_depth.to_be_bytes());
191            }
192
193            for child in references {
194                let child_hash = child.as_ref().hash(level + level_offset);
195                hasher.update(child_hash.as_slice());
196            }
197
198            let hash = hasher.finalize().into();
199            hashes.push((hash, depth));
200        }
201
202        Ok(hashes.into_boxed_slice())
203    }
204}