tycho_block_util/message/
ext_msg_repr.rs1use std::cell::RefCell;
2use std::sync::OnceLock;
3
4use tycho_types::cell::CellTreeStats;
5use tycho_types::error::Error;
6use tycho_types::models::{ExtInMsgInfo, IntAddr, MsgType, StateInit};
7use tycho_types::prelude::*;
8use tycho_util::FastHashMap;
9
10pub async fn validate_external_message(body: &bytes::Bytes) -> Result<(), InvalidExtMsg> {
12 if body.len() > ExtMsgRepr::BOUNDARY_BOC_SIZE {
13 let body = body.clone();
14 tycho_util::sync::rayon_run_fifo(move || ExtMsgRepr::validate(&body).map(|_| ())).await
16 } else {
17 ExtMsgRepr::validate(body).map(|_| ())
18 }
19}
20
21pub async fn parse_external_message(body: &bytes::Bytes) -> Result<Cell, InvalidExtMsg> {
23 if body.len() > ExtMsgRepr::BOUNDARY_BOC_SIZE {
24 let body = body.clone();
25 tycho_util::sync::rayon_run_fifo(move || ExtMsgRepr::validate(&body)).await
26 } else {
27 ExtMsgRepr::validate(body)
28 }
29}
30
31pub fn normalize_external_message(cell: &'_ DynCell) -> Result<Cell, Error> {
35 let mut cs = cell.as_slice()?;
36 if MsgType::load_from(&mut cs)? != MsgType::ExtIn {
37 return Err(Error::InvalidData);
38 }
39
40 let info = ExtInMsgInfo::load_from(&mut cs)?;
41
42 if cs.load_bit()? {
44 if cs.load_bit()? {
45 cs.load_reference()?;
47 } else {
48 StateInit::load_from(&mut cs)?;
50 }
51 }
52
53 let body = if cs.load_bit()? {
55 cs.load_reference_cloned()?
56 } else if cs.is_empty() {
57 Cell::empty_cell()
58 } else {
59 CellBuilder::build_from(cs)?
60 };
61
62 build_normalized_external_message(&info.dst, body)
64}
65
66pub fn build_normalized_external_message(dst: &IntAddr, body: Cell) -> Result<Cell, Error> {
67 let cx = Cell::empty_context();
68 let mut b = CellBuilder::new();
69
70 b.store_small_uint(0b1000, 4)?;
73 dst.store_into(&mut b, cx)?;
75 b.store_small_uint(0b000001, 6)?;
79 b.store_reference(body)?;
80
81 b.build_ext(cx)
82}
83
84static ALLOWED_WORKCHAINS: OnceLock<Vec<i8>> = OnceLock::new();
85
86pub struct ExtMsgRepr;
87
88impl ExtMsgRepr {
89 pub const MAX_BOC_SIZE: usize = 65535;
90 pub const MAX_REPR_DEPTH: u16 = 512;
91 pub const MAX_ALLOWED_MERKLE_DEPTH: u8 = 2;
92 pub const MAX_MSG_BITS: u64 = 1 << 21;
93 pub const MAX_MSG_CELLS: u64 = 1 << 13;
94 pub const BOUNDARY_BOC_SIZE: usize = 1 << 12;
95
96 pub fn set_allowed_workchains(workchains: Vec<i8>) -> anyhow::Result<()> {
97 anyhow::ensure!(
98 ALLOWED_WORKCHAINS.set(workchains).is_ok(),
99 "should run `set_allowed_workchains()` only once",
100 );
101 Ok(())
102 }
103 fn allowed_workchains() -> Option<&'static Vec<i8>> {
104 ALLOWED_WORKCHAINS.get()
105 }
106
107 pub fn validate<T: AsRef<[u8]>>(bytes: T) -> Result<Cell, InvalidExtMsg> {
111 if bytes.as_ref().len() > Self::MAX_BOC_SIZE {
113 return Err(InvalidExtMsg::BocSizeExceeded);
114 }
115
116 let msg_root = Self::boc_decode_with_limit(bytes.as_ref(), Self::MAX_MSG_CELLS)?;
118
119 if msg_root.level() != 0 {
121 return Err(InvalidExtMsg::TooBigLevel);
122 }
123
124 if msg_root.repr_depth() > Self::MAX_REPR_DEPTH {
126 return Err(InvalidExtMsg::DepthExceeded);
127 }
128
129 if msg_root.is_exotic() {
131 return Err(InvalidExtMsg::InvalidMessage(Error::InvalidData));
132 }
133
134 let mut cs = msg_root.as_slice_allow_exotic();
136
137 'info: {
138 if MsgType::load_from(&mut cs)? == MsgType::ExtIn {
140 let info = ExtInMsgInfo::load_from(&mut cs)?;
141 if let IntAddr::Std(std_addr) = &info.dst {
142 let Some(allowed_workchains) = Self::allowed_workchains() else {
144 return Err(InvalidExtMsg::AllowedWorkchainsNotInitialized);
145 };
146 if allowed_workchains.contains(&std_addr.workchain)
147 && std_addr.anycast.is_none()
148 {
149 break 'info;
150 }
151 }
152 }
153
154 return Err(InvalidExtMsg::InvalidMessage(Error::InvalidData));
156 }
157
158 if !MsgStorageStat::check_slice(&cs, Self::MAX_ALLOWED_MERKLE_DEPTH, CellTreeStats {
160 bit_count: Self::MAX_MSG_BITS,
161 cell_count: Self::MAX_MSG_CELLS,
162 }) {
163 return Err(InvalidExtMsg::MsgSizeExceeded);
164 }
165
166 if cs.load_bit()? {
168 if cs.load_bit()? {
169 cs.load_reference().and_then(|c| {
171 let mut cs = c.as_slice()?;
172 StateInit::load_from(&mut cs)?;
173
174 if cs.is_empty() {
176 Ok(())
177 } else {
178 Err(Error::InvalidData)
179 }
180 })?;
181 } else {
182 StateInit::load_from(&mut cs)?;
184 }
185 }
186
187 if cs.load_bit()? {
189 if !cs.is_data_empty() || cs.size_refs() != 1 {
191 return Err(InvalidExtMsg::InvalidMessage(Error::InvalidData));
192 }
193 }
194
195 Ok(msg_root)
196 }
197
198 fn boc_decode_with_limit(data: &[u8], max_cells: u64) -> Result<Cell, InvalidExtMsg> {
199 use tycho_types::boc::de::{self, Options};
200
201 let header = tycho_types::boc::de::BocHeader::decode(data, &Options {
202 max_roots: Some(1),
203 min_roots: Some(1),
204 })?;
205
206 if header.cells().len() as u64 > max_cells {
208 return Err(InvalidExtMsg::MsgSizeExceeded);
209 }
210
211 if let Some(&root) = header.roots().first() {
212 let cells = header.finalize(Cell::empty_context())?;
213 if let Some(root) = cells.get(root) {
214 return Ok(root);
215 }
216 }
217
218 Err(InvalidExtMsg::BocError(de::Error::RootCellNotFound))
219 }
220}
221
222#[derive(Debug, thiserror::Error)]
223pub enum InvalidExtMsg {
224 #[error("BOC size exceeds maximum allowed size")]
225 BocSizeExceeded,
226 #[error("invalid message BOC")]
227 BocError(#[from] tycho_types::boc::de::Error),
228 #[error("too big root cell level")]
229 TooBigLevel,
230 #[error("max cell repr depth exceeded")]
231 DepthExceeded,
232 #[error("invalid message")]
233 InvalidMessage(#[from] Error),
234 #[error("message size limits exceeded")]
235 MsgSizeExceeded,
236 #[error(
237 "first should run `ExtMsgRepr::set_allowed_workchains()` to set up expected workchains ids"
238 )]
239 AllowedWorkchainsNotInitialized,
240}
241
242pub struct MsgStorageStat<'a> {
243 visited: &'a mut FastHashMap<&'static HashBytes, u8>,
244 limits: CellTreeStats,
245 max_merkle_depth: u8,
246 cells: u64,
247 bits: u64,
248}
249
250impl<'a> MsgStorageStat<'a> {
251 thread_local! {
252 static VISITED_CELLS: RefCell<FastHashMap<&'static HashBytes, u8>> = RefCell::new(
254 FastHashMap::with_capacity_and_hasher(128, Default::default()),
255 );
256 }
257
258 pub fn check_slice<'c: 'a>(
259 cs: &CellSlice<'c>,
260 max_merkle_depth: u8,
261 limits: CellTreeStats,
262 ) -> bool {
263 MsgStorageStat::VISITED_CELLS.with_borrow_mut(|visited| {
264 let res =
266 unsafe { MsgStorageStat::check_slice_impl(visited, cs, max_merkle_depth, limits) };
267 visited.clear();
268 res
269 })
270 }
271
272 unsafe fn check_slice_impl(
278 visited: &'a mut FastHashMap<&'static HashBytes, u8>,
279 cs: &CellSlice<'_>,
280 max_merkle_depth: u8,
281 limits: CellTreeStats,
282 ) -> bool {
283 debug_assert!(visited.is_empty());
284
285 let mut state = Self {
286 visited,
287 limits,
288 max_merkle_depth,
289 cells: 1,
290 bits: cs.size_bits() as u64,
291 };
292
293 for cell in cs.references() {
294 if unsafe { state.add_cell(cell) }.is_none() {
295 return false;
296 }
297 }
298
299 true
300 }
301
302 unsafe fn add_cell(&mut self, cell: &DynCell) -> Option<u8> {
303 if let Some(merkle_depth) = self.visited.get(cell.repr_hash()) {
304 return Some(*merkle_depth);
305 }
306
307 self.cells = self.cells.checked_add(1)?;
308 self.bits = self.bits.checked_add(cell.bit_len() as u64)?;
309
310 if self.cells > self.limits.cell_count || self.bits > self.limits.bit_count {
311 return None;
312 }
313
314 let mut max_merkle_depth = 0u8;
315 for cell in cell.references() {
316 max_merkle_depth = std::cmp::max(unsafe { self.add_cell(cell)? }, max_merkle_depth);
317 }
318 max_merkle_depth = max_merkle_depth.saturating_add(cell.cell_type().is_merkle() as u8);
319
320 self.visited.insert(
322 unsafe { std::mem::transmute::<&HashBytes, &'static HashBytes>(cell.repr_hash()) },
323 max_merkle_depth,
324 );
325
326 (max_merkle_depth <= self.max_merkle_depth).then_some(max_merkle_depth)
327 }
328}
329
330#[cfg(test)]
331mod test {
332 use tycho_types::error::Error;
333 use tycho_types::merkle::MerkleProof;
334 use tycho_types::models::{ExtOutMsgInfo, IntMsgInfo, MessageLayout, MsgInfo, OwnedMessage};
335
336 use super::*;
337 use crate::block::AlwaysInclude;
338
339 #[test]
340 fn fits_into_limits() -> anyhow::Result<()> {
341 let _ = ExtMsgRepr::set_allowed_workchains(vec![-1, 0]);
342
343 #[track_caller]
344 fn unwrap_msg(cell: Cell) {
345 let boc = Boc::encode(cell);
346 ExtMsgRepr::validate(boc).unwrap();
347 }
348
349 unwrap_msg(CellBuilder::build_from(OwnedMessage {
351 info: MsgInfo::ExtIn(Default::default()),
352 init: None,
353 body: Default::default(),
354 layout: None,
355 })?);
356
357 unwrap_msg({
359 let mut count = 0;
360 let body = make_big_tree(8, &mut count, ExtMsgRepr::MAX_MSG_CELLS as u16 - 100);
361 println!("{count}");
362
363 CellBuilder::build_from(OwnedMessage {
364 info: MsgInfo::ExtIn(Default::default()),
365 init: None,
366 body: body.into(),
367 layout: None,
368 })?
369 });
370
371 unwrap_msg({
373 let leaf_proof = MerkleProof::create(Cell::empty_cell_ref(), AlwaysInclude)
374 .build()
375 .and_then(CellBuilder::build_from)?;
376
377 let body = MerkleProof::create(leaf_proof.as_ref(), AlwaysInclude)
378 .build()
379 .and_then(CellBuilder::build_from)?;
380
381 CellBuilder::build_from(OwnedMessage {
382 info: MsgInfo::ExtIn(Default::default()),
383 init: None,
384 body: body.into(),
385 layout: Some(MessageLayout {
386 body_to_cell: true,
387 init_to_cell: false,
388 }),
389 })?
390 });
391
392 Ok(())
393 }
394
395 #[test]
396 fn dont_fit_into_limits() -> anyhow::Result<()> {
397 let _ = ExtMsgRepr::set_allowed_workchains(vec![-1, 0]);
398
399 #[track_caller]
400 fn expect_err(cell: Cell) -> InvalidExtMsg {
401 let boc = Boc::encode(cell);
402 ExtMsgRepr::validate(boc).unwrap_err()
403 }
404
405 assert!(matches!(
407 expect_err(Cell::empty_cell()),
408 InvalidExtMsg::InvalidMessage(Error::CellUnderflow)
409 ));
410
411 assert!(matches!(
413 expect_err(CellBuilder::build_from(MerkleProof::default())?),
414 InvalidExtMsg::InvalidMessage(Error::InvalidData)
415 ));
416
417 {
419 let mut cell = Cell::default();
420 for _ in 0..520 {
421 cell = CellBuilder::build_from(cell)?;
422 }
423 assert!(matches!(expect_err(cell), InvalidExtMsg::DepthExceeded));
424 }
425
426 {
428 let cell = CellBuilder::build_from(OwnedMessage {
429 info: MsgInfo::Int(IntMsgInfo::default()),
430 init: None,
431 body: Default::default(),
432 layout: None,
433 })?;
434 assert!(matches!(
435 expect_err(cell),
436 InvalidExtMsg::InvalidMessage(Error::InvalidData)
437 ));
438
439 let cell = CellBuilder::build_from(OwnedMessage {
440 info: MsgInfo::ExtOut(ExtOutMsgInfo::default()),
441 init: None,
442 body: Default::default(),
443 layout: None,
444 })?;
445 assert!(matches!(
446 expect_err(cell),
447 InvalidExtMsg::InvalidMessage(Error::InvalidData)
448 ));
449 }
450
451 {
453 let mut b = CellBuilder::new();
454 OwnedMessage {
455 info: MsgInfo::ExtOut(ExtOutMsgInfo::default()),
456 init: None,
457 body: Default::default(),
458 layout: Some(MessageLayout {
459 body_to_cell: true,
460 init_to_cell: false,
461 }),
462 }
463 .store_into(&mut b, Cell::empty_context())?;
464
465 assert!(matches!(
467 expect_err({
468 let mut b = b.clone();
469 b.store_u16(123)?;
470 b.build()?
471 }),
472 InvalidExtMsg::InvalidMessage(Error::InvalidData)
473 ));
474
475 assert!(matches!(
477 expect_err({
478 let mut b = b.clone();
479 b.store_reference(Cell::empty_cell())?;
480 b.build()?
481 }),
482 InvalidExtMsg::InvalidMessage(Error::InvalidData)
483 ));
484
485 assert!(matches!(
487 expect_err({
488 let mut b = b.clone();
489 b.store_u16(123)?;
490 b.store_reference(Cell::empty_cell())?;
491 b.build()?
492 }),
493 InvalidExtMsg::InvalidMessage(Error::InvalidData)
494 ));
495 }
496
497 {
499 let cell = exceed_big_message()?;
500 assert!(matches!(expect_err(cell), InvalidExtMsg::MsgSizeExceeded));
501 }
502
503 {
505 let cell = create_deep_merkle()?;
506 assert!(matches!(expect_err(cell), InvalidExtMsg::MsgSizeExceeded));
507 }
508
509 Ok(())
510 }
511
512 fn exceed_big_message() -> anyhow::Result<Cell> {
513 let mut count = 0;
514 let body = make_big_tree(8, &mut count, ExtMsgRepr::MAX_MSG_CELLS as u16 + 100);
515
516 let cell = CellBuilder::build_from(OwnedMessage {
517 info: MsgInfo::ExtIn(Default::default()),
518 init: None,
519 body: body.into(),
520 layout: None,
521 })?;
522
523 Ok(cell)
524 }
525
526 fn create_deep_merkle() -> anyhow::Result<Cell> {
527 let leaf_proof = MerkleProof::create(Cell::empty_cell_ref(), AlwaysInclude)
528 .build()
529 .and_then(CellBuilder::build_from)?;
530
531 let inner_proof = MerkleProof::create(leaf_proof.as_ref(), AlwaysInclude)
532 .build()
533 .and_then(CellBuilder::build_from)?;
534
535 let body = MerkleProof::create(inner_proof.as_ref(), AlwaysInclude)
536 .build()
537 .and_then(CellBuilder::build_from)?;
538
539 let cell = CellBuilder::build_from(OwnedMessage {
540 info: MsgInfo::ExtIn(Default::default()),
541 init: None,
542 body: body.into(),
543 layout: Some(MessageLayout {
544 body_to_cell: true,
545 init_to_cell: false,
546 }),
547 })?;
548
549 Ok(cell)
550 }
551
552 fn make_big_tree(depth: u8, count: &mut u16, target: u16) -> Cell {
553 *count += 1;
554
555 if depth == 0 {
556 CellBuilder::build_from(*count).unwrap()
557 } else {
558 let mut b = CellBuilder::new();
559 for _ in 0..4 {
560 if *count < target {
561 b.store_reference(make_big_tree(depth - 1, count, target))
562 .unwrap();
563 }
564 }
565 b.build().unwrap()
566 }
567 }
568}