tycho_block_util/message/
ext_msg_repr.rs

1use std::cell::RefCell;
2
3use tycho_types::cell::CellTreeStats;
4use tycho_types::error::Error;
5use tycho_types::models::{ExtInMsgInfo, IntAddr, MsgType, StateInit};
6use tycho_types::prelude::*;
7use tycho_util::FastHashMap;
8
9pub async fn validate_external_message(body: &bytes::Bytes) -> Result<(), InvalidExtMsg> {
10    if body.len() > ExtMsgRepr::BOUNDARY_BOC_SIZE {
11        let body = body.clone();
12        // NOTE: Drop `Cell` inside rayon to not block the executor thread.
13        tycho_util::sync::rayon_run_fifo(move || ExtMsgRepr::validate(&body).map(|_| ())).await
14    } else {
15        ExtMsgRepr::validate(body).map(|_| ())
16    }
17}
18
19pub async fn parse_external_message(body: &bytes::Bytes) -> Result<Cell, InvalidExtMsg> {
20    if body.len() > ExtMsgRepr::BOUNDARY_BOC_SIZE {
21        let body = body.clone();
22        tycho_util::sync::rayon_run_fifo(move || ExtMsgRepr::validate(&body)).await
23    } else {
24        ExtMsgRepr::validate(body)
25    }
26}
27
28/// Computes a normalized message.
29///
30/// A normalized message contains only `dst` address and a body as reference.
31pub fn normalize_external_message(cell: &'_ DynCell) -> Result<Cell, Error> {
32    let mut cs = cell.as_slice()?;
33    if MsgType::load_from(&mut cs)? != MsgType::ExtIn {
34        return Err(Error::InvalidData);
35    }
36
37    let info = ExtInMsgInfo::load_from(&mut cs)?;
38
39    // Skip message state init.
40    if cs.load_bit()? {
41        if cs.load_bit()? {
42            // State init as reference.
43            cs.load_reference()?;
44        } else {
45            // Inline state init.
46            StateInit::load_from(&mut cs)?;
47        }
48    }
49
50    // Load message body.
51    let body = if cs.load_bit()? {
52        cs.load_reference_cloned()?
53    } else if cs.is_empty() {
54        Cell::empty_cell()
55    } else {
56        CellBuilder::build_from(cs)?
57    };
58
59    // Rebuild normalized message.
60    build_normalized_external_message(&info.dst, body)
61}
62
63pub fn build_normalized_external_message(dst: &IntAddr, body: Cell) -> Result<Cell, Error> {
64    let cx = Cell::empty_context();
65    let mut b = CellBuilder::new();
66
67    // message$_ -> info:CommonMsgInfo -> ext_in_msg_info$10
68    // message$_ -> info:CommonMsgInfo -> src:MsgAddressExt -> addr_none$00
69    b.store_small_uint(0b1000, 4)?;
70    // message$_ -> info:CommonMsgInfo -> dest:MsgAddressInt
71    dst.store_into(&mut b, cx)?;
72    // message$_ -> info:CommonMsgInfo -> import_fee:Grams -> 0
73    // message$_ -> init:(Maybe (Either StateInit ^StateInit)) -> nothing$0
74    // message$_ -> body:(Either X ^X) -> right$1
75    b.store_small_uint(0b000001, 6)?;
76    b.store_reference(body)?;
77
78    b.build_ext(cx)
79}
80
81pub struct ExtMsgRepr;
82
83impl ExtMsgRepr {
84    pub const MAX_BOC_SIZE: usize = 65535;
85    pub const MAX_REPR_DEPTH: u16 = 512;
86    pub const MAX_ALLOWED_MERKLE_DEPTH: u8 = 2;
87    pub const MAX_MSG_BITS: u64 = 1 << 21;
88    pub const MAX_MSG_CELLS: u64 = 1 << 13;
89    pub const BOUNDARY_BOC_SIZE: usize = 1 << 12;
90
91    // === General methods ===
92
93    pub fn validate<T: AsRef<[u8]>>(bytes: T) -> Result<Cell, InvalidExtMsg> {
94        // Apply limits to the encoded BOC.
95        if bytes.as_ref().len() > Self::MAX_BOC_SIZE {
96            return Err(InvalidExtMsg::BocSizeExceeded);
97        }
98
99        // Decode BOC.
100        let msg_root = Self::boc_decode_with_limit(bytes.as_ref(), Self::MAX_MSG_CELLS)?;
101
102        // Cell must not contain any suspicious pruned branches not wrapped into merkle stuff.
103        if msg_root.level() != 0 {
104            return Err(InvalidExtMsg::TooBigLevel);
105        }
106
107        // Apply limits to the cell depth.
108        if msg_root.repr_depth() > Self::MAX_REPR_DEPTH {
109            return Err(InvalidExtMsg::DepthExceeded);
110        }
111
112        // External message must be an ordinary cell.
113        if msg_root.is_exotic() {
114            return Err(InvalidExtMsg::InvalidMessage(Error::InvalidData));
115        }
116
117        // Start parsing the message (we are sure now that it is an ordinary cell).
118        let mut cs = msg_root.as_slice_allow_exotic();
119
120        'info: {
121            // Only external inbound messages are allowed.
122            if MsgType::load_from(&mut cs)? == MsgType::ExtIn {
123                let info = ExtInMsgInfo::load_from(&mut cs)?;
124                if let IntAddr::Std(std_addr) = &info.dst
125                    && std_addr.anycast.is_none()
126                {
127                    break 'info;
128                }
129            }
130
131            // All other cases are considered garbage.
132            return Err(InvalidExtMsg::InvalidMessage(Error::InvalidData));
133        }
134
135        // Check limits with the remaining slice.
136        if !MsgStorageStat::check_slice(&cs, Self::MAX_ALLOWED_MERKLE_DEPTH, CellTreeStats {
137            bit_count: Self::MAX_MSG_BITS,
138            cell_count: Self::MAX_MSG_CELLS,
139        }) {
140            return Err(InvalidExtMsg::MsgSizeExceeded);
141        }
142
143        // Process message state init.
144        if cs.load_bit()? {
145            if cs.load_bit()? {
146                // State init as reference.
147                cs.load_reference().and_then(|c| {
148                    let mut cs = c.as_slice()?;
149                    StateInit::load_from(&mut cs)?;
150
151                    // State init cell must not contain anything else.
152                    if cs.is_empty() {
153                        Ok(())
154                    } else {
155                        Err(Error::InvalidData)
156                    }
157                })?;
158            } else {
159                // Inline state init.
160                StateInit::load_from(&mut cs)?;
161            }
162        }
163
164        // Process message body.
165        if cs.load_bit()? {
166            // Message must not contain anything other than body as cell.
167            if !cs.is_data_empty() || cs.size_refs() != 1 {
168                return Err(InvalidExtMsg::InvalidMessage(Error::InvalidData));
169            }
170        }
171
172        Ok(msg_root)
173    }
174
175    fn boc_decode_with_limit(data: &[u8], max_cells: u64) -> Result<Cell, InvalidExtMsg> {
176        use tycho_types::boc::de::{self, Options};
177
178        let header = tycho_types::boc::de::BocHeader::decode(data, &Options {
179            max_roots: Some(1),
180            min_roots: Some(1),
181        })?;
182
183        // Optimistic check based on just cell data ranges.
184        if header.cells().len() as u64 > max_cells {
185            return Err(InvalidExtMsg::MsgSizeExceeded);
186        }
187
188        if let Some(&root) = header.roots().first() {
189            let cells = header.finalize(Cell::empty_context())?;
190            if let Some(root) = cells.get(root) {
191                return Ok(root);
192            }
193        }
194
195        Err(InvalidExtMsg::BocError(de::Error::RootCellNotFound))
196    }
197}
198
199#[derive(Debug, thiserror::Error)]
200pub enum InvalidExtMsg {
201    #[error("BOC size exceeds maximum allowed size")]
202    BocSizeExceeded,
203    #[error("invalid message BOC")]
204    BocError(#[from] tycho_types::boc::de::Error),
205    #[error("too big root cell level")]
206    TooBigLevel,
207    #[error("max cell repr depth exceeded")]
208    DepthExceeded,
209    #[error("invalid message")]
210    InvalidMessage(#[from] Error),
211    #[error("message size limits exceeded")]
212    MsgSizeExceeded,
213}
214
215pub struct MsgStorageStat<'a> {
216    visited: &'a mut FastHashMap<&'static HashBytes, u8>,
217    limits: CellTreeStats,
218    max_merkle_depth: u8,
219    cells: u64,
220    bits: u64,
221}
222
223impl<'a> MsgStorageStat<'a> {
224    thread_local! {
225        /// Storage to reuse for parsing messages.
226        static VISITED_CELLS: RefCell<FastHashMap<&'static HashBytes, u8>> = RefCell::new(
227            FastHashMap::with_capacity_and_hasher(128, Default::default()),
228        );
229    }
230
231    pub fn check_slice<'c: 'a>(
232        cs: &CellSlice<'c>,
233        max_merkle_depth: u8,
234        limits: CellTreeStats,
235    ) -> bool {
236        MsgStorageStat::VISITED_CELLS.with_borrow_mut(|visited| {
237            // SAFETY: We are clearing the `visited` map right after the call.
238            let res =
239                unsafe { MsgStorageStat::check_slice_impl(visited, cs, max_merkle_depth, limits) };
240            visited.clear();
241            res
242        })
243    }
244
245    /// # Safety
246    ///
247    /// The following must be true:
248    /// - `visited` must be empty;
249    /// - `visited` must be cleared right after this call.
250    unsafe fn check_slice_impl(
251        visited: &'a mut FastHashMap<&'static HashBytes, u8>,
252        cs: &CellSlice<'_>,
253        max_merkle_depth: u8,
254        limits: CellTreeStats,
255    ) -> bool {
256        debug_assert!(visited.is_empty());
257
258        let mut state = Self {
259            visited,
260            limits,
261            max_merkle_depth,
262            cells: 1,
263            bits: cs.size_bits() as u64,
264        };
265
266        for cell in cs.references() {
267            if unsafe { state.add_cell(cell) }.is_none() {
268                return false;
269            }
270        }
271
272        true
273    }
274
275    unsafe fn add_cell(&mut self, cell: &DynCell) -> Option<u8> {
276        if let Some(merkle_depth) = self.visited.get(cell.repr_hash()) {
277            return Some(*merkle_depth);
278        }
279
280        self.cells = self.cells.checked_add(1)?;
281        self.bits = self.bits.checked_add(cell.bit_len() as u64)?;
282
283        if self.cells > self.limits.cell_count || self.bits > self.limits.bit_count {
284            return None;
285        }
286
287        let mut max_merkle_depth = 0u8;
288        for cell in cell.references() {
289            max_merkle_depth = std::cmp::max(unsafe { self.add_cell(cell)? }, max_merkle_depth);
290        }
291        max_merkle_depth = max_merkle_depth.saturating_add(cell.cell_type().is_merkle() as u8);
292
293        // SAFETY: `visited` must be cleared before dropping the original cell.
294        self.visited.insert(
295            unsafe { std::mem::transmute::<&HashBytes, &'static HashBytes>(cell.repr_hash()) },
296            max_merkle_depth,
297        );
298
299        (max_merkle_depth <= self.max_merkle_depth).then_some(max_merkle_depth)
300    }
301}
302
303#[cfg(test)]
304mod test {
305    use tycho_types::error::Error;
306    use tycho_types::merkle::MerkleProof;
307    use tycho_types::models::{ExtOutMsgInfo, IntMsgInfo, MessageLayout, MsgInfo, OwnedMessage};
308
309    use super::*;
310    use crate::block::AlwaysInclude;
311
312    #[test]
313    fn fits_into_limits() -> anyhow::Result<()> {
314        #[track_caller]
315        fn unwrap_msg(cell: Cell) {
316            let boc = Boc::encode(cell);
317            ExtMsgRepr::validate(boc).unwrap();
318        }
319
320        // Simple message.
321        unwrap_msg(CellBuilder::build_from(OwnedMessage {
322            info: MsgInfo::ExtIn(Default::default()),
323            init: None,
324            body: Default::default(),
325            layout: None,
326        })?);
327
328        // Big message.
329        unwrap_msg({
330            let mut count = 0;
331            let body = make_big_tree(8, &mut count, ExtMsgRepr::MAX_MSG_CELLS as u16 - 100);
332            println!("{count}");
333
334            CellBuilder::build_from(OwnedMessage {
335                info: MsgInfo::ExtIn(Default::default()),
336                init: None,
337                body: body.into(),
338                layout: None,
339            })?
340        });
341
342        // Close enough merkle depth.
343        unwrap_msg({
344            let leaf_proof = MerkleProof::create(Cell::empty_cell_ref(), AlwaysInclude)
345                .build()
346                .and_then(CellBuilder::build_from)?;
347
348            let body = MerkleProof::create(leaf_proof.as_ref(), AlwaysInclude)
349                .build()
350                .and_then(CellBuilder::build_from)?;
351
352            CellBuilder::build_from(OwnedMessage {
353                info: MsgInfo::ExtIn(Default::default()),
354                init: None,
355                body: body.into(),
356                layout: Some(MessageLayout {
357                    body_to_cell: true,
358                    init_to_cell: false,
359                }),
360            })?
361        });
362
363        Ok(())
364    }
365
366    #[test]
367    fn dont_fit_into_limits() -> anyhow::Result<()> {
368        #[track_caller]
369        fn expect_err(cell: Cell) -> InvalidExtMsg {
370            let boc = Boc::encode(cell);
371            ExtMsgRepr::validate(boc).unwrap_err()
372        }
373
374        // Garbage.
375        assert!(matches!(
376            expect_err(Cell::empty_cell()),
377            InvalidExtMsg::InvalidMessage(Error::CellUnderflow)
378        ));
379
380        // Exotic cells.
381        assert!(matches!(
382            expect_err(CellBuilder::build_from(MerkleProof::default())?),
383            InvalidExtMsg::InvalidMessage(Error::InvalidData)
384        ));
385
386        // Too deep cells tree.
387        {
388            let mut cell = Cell::default();
389            for _ in 0..520 {
390                cell = CellBuilder::build_from(cell)?;
391            }
392            assert!(matches!(expect_err(cell), InvalidExtMsg::DepthExceeded));
393        }
394
395        // Non-external message.
396        {
397            let cell = CellBuilder::build_from(OwnedMessage {
398                info: MsgInfo::Int(IntMsgInfo::default()),
399                init: None,
400                body: Default::default(),
401                layout: None,
402            })?;
403            assert!(matches!(
404                expect_err(cell),
405                InvalidExtMsg::InvalidMessage(Error::InvalidData)
406            ));
407
408            let cell = CellBuilder::build_from(OwnedMessage {
409                info: MsgInfo::ExtOut(ExtOutMsgInfo::default()),
410                init: None,
411                body: Default::default(),
412                layout: None,
413            })?;
414            assert!(matches!(
415                expect_err(cell),
416                InvalidExtMsg::InvalidMessage(Error::InvalidData)
417            ));
418        }
419
420        // External message with extra data.
421        {
422            let mut b = CellBuilder::new();
423            OwnedMessage {
424                info: MsgInfo::ExtOut(ExtOutMsgInfo::default()),
425                init: None,
426                body: Default::default(),
427                layout: Some(MessageLayout {
428                    body_to_cell: true,
429                    init_to_cell: false,
430                }),
431            }
432            .store_into(&mut b, Cell::empty_context())?;
433
434            // Bits
435            assert!(matches!(
436                expect_err({
437                    let mut b = b.clone();
438                    b.store_u16(123)?;
439                    b.build()?
440                }),
441                InvalidExtMsg::InvalidMessage(Error::InvalidData)
442            ));
443
444            // Refs
445            assert!(matches!(
446                expect_err({
447                    let mut b = b.clone();
448                    b.store_reference(Cell::empty_cell())?;
449                    b.build()?
450                }),
451                InvalidExtMsg::InvalidMessage(Error::InvalidData)
452            ));
453
454            // Both
455            assert!(matches!(
456                expect_err({
457                    let mut b = b.clone();
458                    b.store_u16(123)?;
459                    b.store_reference(Cell::empty_cell())?;
460                    b.build()?
461                }),
462                InvalidExtMsg::InvalidMessage(Error::InvalidData)
463            ));
464        }
465
466        // Too big message.
467        {
468            let cell = exceed_big_message()?;
469            assert!(matches!(expect_err(cell), InvalidExtMsg::MsgSizeExceeded));
470        }
471
472        // Too big merkle depth.
473        {
474            let cell = create_deep_merkle()?;
475            assert!(matches!(expect_err(cell), InvalidExtMsg::MsgSizeExceeded));
476        }
477
478        Ok(())
479    }
480
481    fn exceed_big_message() -> anyhow::Result<Cell> {
482        let mut count = 0;
483        let body = make_big_tree(8, &mut count, ExtMsgRepr::MAX_MSG_CELLS as u16 + 100);
484
485        let cell = CellBuilder::build_from(OwnedMessage {
486            info: MsgInfo::ExtIn(Default::default()),
487            init: None,
488            body: body.into(),
489            layout: None,
490        })?;
491
492        Ok(cell)
493    }
494
495    fn create_deep_merkle() -> anyhow::Result<Cell> {
496        let leaf_proof = MerkleProof::create(Cell::empty_cell_ref(), AlwaysInclude)
497            .build()
498            .and_then(CellBuilder::build_from)?;
499
500        let inner_proof = MerkleProof::create(leaf_proof.as_ref(), AlwaysInclude)
501            .build()
502            .and_then(CellBuilder::build_from)?;
503
504        let body = MerkleProof::create(inner_proof.as_ref(), AlwaysInclude)
505            .build()
506            .and_then(CellBuilder::build_from)?;
507
508        let cell = CellBuilder::build_from(OwnedMessage {
509            info: MsgInfo::ExtIn(Default::default()),
510            init: None,
511            body: body.into(),
512            layout: Some(MessageLayout {
513                body_to_cell: true,
514                init_to_cell: false,
515            }),
516        })?;
517
518        Ok(cell)
519    }
520
521    fn make_big_tree(depth: u8, count: &mut u16, target: u16) -> Cell {
522        *count += 1;
523
524        if depth == 0 {
525            CellBuilder::build_from(*count).unwrap()
526        } else {
527            let mut b = CellBuilder::new();
528            for _ in 0..4 {
529                if *count < target {
530                    b.store_reference(make_big_tree(depth - 1, count, target))
531                        .unwrap();
532                }
533            }
534            b.build().unwrap()
535        }
536    }
537}