tycho_block_util/message/
ext_msg_repr.rs1use 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 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
28pub 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 if cs.load_bit()? {
41 if cs.load_bit()? {
42 cs.load_reference()?;
44 } else {
45 StateInit::load_from(&mut cs)?;
47 }
48 }
49
50 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 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 b.store_small_uint(0b1000, 4)?;
70 dst.store_into(&mut b, cx)?;
72 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 pub fn validate<T: AsRef<[u8]>>(bytes: T) -> Result<Cell, InvalidExtMsg> {
94 if bytes.as_ref().len() > Self::MAX_BOC_SIZE {
96 return Err(InvalidExtMsg::BocSizeExceeded);
97 }
98
99 let msg_root = Self::boc_decode_with_limit(bytes.as_ref(), Self::MAX_MSG_CELLS)?;
101
102 if msg_root.level() != 0 {
104 return Err(InvalidExtMsg::TooBigLevel);
105 }
106
107 if msg_root.repr_depth() > Self::MAX_REPR_DEPTH {
109 return Err(InvalidExtMsg::DepthExceeded);
110 }
111
112 if msg_root.is_exotic() {
114 return Err(InvalidExtMsg::InvalidMessage(Error::InvalidData));
115 }
116
117 let mut cs = msg_root.as_slice_allow_exotic();
119
120 'info: {
121 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 return Err(InvalidExtMsg::InvalidMessage(Error::InvalidData));
133 }
134
135 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 if cs.load_bit()? {
145 if cs.load_bit()? {
146 cs.load_reference().and_then(|c| {
148 let mut cs = c.as_slice()?;
149 StateInit::load_from(&mut cs)?;
150
151 if cs.is_empty() {
153 Ok(())
154 } else {
155 Err(Error::InvalidData)
156 }
157 })?;
158 } else {
159 StateInit::load_from(&mut cs)?;
161 }
162 }
163
164 if cs.load_bit()? {
166 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 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 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 let res =
239 unsafe { MsgStorageStat::check_slice_impl(visited, cs, max_merkle_depth, limits) };
240 visited.clear();
241 res
242 })
243 }
244
245 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 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 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 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 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 assert!(matches!(
376 expect_err(Cell::empty_cell()),
377 InvalidExtMsg::InvalidMessage(Error::CellUnderflow)
378 ));
379
380 assert!(matches!(
382 expect_err(CellBuilder::build_from(MerkleProof::default())?),
383 InvalidExtMsg::InvalidMessage(Error::InvalidData)
384 ));
385
386 {
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 {
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 {
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 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 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 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 {
468 let cell = exceed_big_message()?;
469 assert!(matches!(expect_err(cell), InvalidExtMsg::MsgSizeExceeded));
470 }
471
472 {
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}