Skip to main content

tycho_block_util/block/
top_blocks.rs

1use anyhow::Result;
2use tycho_types::models::*;
3use tycho_util::FastHashMap;
4
5use crate::block::BlockStuff;
6
7/// A map from shard identifiers to the last block seqno.
8#[derive(Debug, Default, Clone)]
9pub struct ShardHeights(FastHashMap<ShardIdent, u32>);
10
11impl ShardHeights {
12    /// Checks whether the given block is equal to or greater than
13    /// the last block for the given shard.
14    pub fn contains(&self, block_id: &BlockId) -> bool {
15        self.contains_shard_seqno(&block_id.shard, block_id.seqno)
16    }
17
18    /// Checks whether the given block satisfies the comparator.
19    ///
20    /// Comparator: `f(top_seqno, block_id.seqno)`.
21    pub fn contains_ext<F>(&self, block_id: &BlockId, f: F) -> bool
22    where
23        F: Fn(u32, u32) -> bool,
24    {
25        self.contains_shard_seqno_ext(&block_id.shard, block_id.seqno, f)
26    }
27
28    /// Checks whether the given pair of [`ShardIdent`] and seqno
29    /// is equal to or greater than the last block for the given shard.
30    ///
31    /// NOTE: Specified shard could be split or merged
32    pub fn contains_shard_seqno(&self, shard_ident: &ShardIdent, seqno: u32) -> bool {
33        self.contains_shard_seqno_ext(shard_ident, seqno, |top_seqno, seqno| top_seqno <= seqno)
34    }
35
36    /// Checks whether the given pair of [`ShardIdent`] and seqno
37    /// satisfies the comparator.
38    ///
39    /// Comparator: `f(top_seqno, seqno)`.
40    ///
41    /// NOTE: Specified shard could be split or merged
42    pub fn contains_shard_seqno_ext<F>(&self, shard_ident: &ShardIdent, seqno: u32, f: F) -> bool
43    where
44        F: Fn(u32, u32) -> bool,
45    {
46        match self.0.get(shard_ident) {
47            Some(&top_seqno) => f(top_seqno, seqno),
48            None => self
49                .0
50                .iter()
51                .find(|&(shard, _)| shard_ident.intersects(shard))
52                .map(|(_, &top_seqno)| f(top_seqno, seqno))
53                .unwrap_or_default(),
54        }
55    }
56
57    /// Returns block count.
58    pub fn len(&self) -> usize {
59        self.0.len()
60    }
61
62    /// Returns whether the map is empty.
63    pub fn is_empty(&self) -> bool {
64        self.0.is_empty()
65    }
66
67    pub fn iter(&self) -> impl ExactSizeIterator<Item = BlockIdShort> + Clone + '_ {
68        self.0
69            .iter()
70            .map(|(shard, seqno)| BlockIdShort::from((*shard, *seqno)))
71    }
72}
73
74impl<const N: usize> From<[(ShardIdent, u32); N]> for ShardHeights {
75    fn from(value: [(ShardIdent, u32); N]) -> Self {
76        Self(FastHashMap::from_iter(value))
77    }
78}
79
80impl FromIterator<(ShardIdent, u32)> for ShardHeights {
81    #[inline]
82    fn from_iter<T: IntoIterator<Item = (ShardIdent, u32)>>(iter: T) -> Self {
83        Self(FastHashMap::from_iter(iter))
84    }
85}
86
87impl FromIterator<BlockIdShort> for ShardHeights {
88    fn from_iter<T: IntoIterator<Item = BlockIdShort>>(iter: T) -> Self {
89        Self(
90            iter.into_iter()
91                .map(|block_id| (block_id.shard, block_id.seqno))
92                .collect(),
93        )
94    }
95}
96
97impl From<FastHashMap<ShardIdent, u32>> for ShardHeights {
98    #[inline]
99    fn from(map: FastHashMap<ShardIdent, u32>) -> Self {
100        Self(map)
101    }
102}
103
104/// Stores last blocks for each workchain and shard.
105#[derive(Debug, Clone)]
106pub struct TopBlocks {
107    pub mc_block: BlockIdShort,
108    pub shard_heights: ShardHeights,
109}
110
111impl TopBlocks {
112    /// Extracts last blocks for each workchain and shard from the given masterchain block.
113    pub fn from_mc_block(mc_block_data: &BlockStuff) -> Result<Self> {
114        let block_id = mc_block_data.id();
115        debug_assert!(block_id.shard.is_masterchain());
116
117        Ok(Self {
118            mc_block: block_id.as_short_id(),
119            shard_heights: ShardHeights(mc_block_data.shard_blocks_seqno()?),
120        })
121    }
122
123    /// Masterchain block seqno
124    pub fn mc_seqno(&self) -> u32 {
125        self.mc_block.seqno
126    }
127
128    pub fn shard_heights(&self) -> &ShardHeights {
129        &self.shard_heights
130    }
131
132    /// Returns block count (including masterchain).
133    pub fn count(&self) -> usize {
134        1 + self.shard_heights.len()
135    }
136
137    /// Checks whether the given block is equal to or greater than
138    /// the last block for the given shard.
139    pub fn contains(&self, block_id: &BlockId) -> bool {
140        self.contains_shard_seqno(&block_id.shard, block_id.seqno)
141    }
142
143    /// Checks whether the given pair of [`ShardIdent`] and seqno
144    /// is equal to or greater than the last block for the given shard.
145    ///
146    /// NOTE: Specified shard could be split or merged
147    pub fn contains_shard_seqno(&self, shard_ident: &ShardIdent, seqno: u32) -> bool {
148        if shard_ident.is_masterchain() {
149            seqno >= self.mc_block.seqno
150        } else {
151            self.shard_heights.contains_shard_seqno(shard_ident, seqno)
152        }
153    }
154
155    /// Returns an iterator over the short ids of the latest blocks.
156    pub fn short_ids(&self) -> TopBlocksShortIdsIter<'_> {
157        TopBlocksShortIdsIter {
158            top_blocks: self,
159            shards_iter: None,
160        }
161    }
162}
163
164/// Iterator over the short ids of the latest blocks.
165pub struct TopBlocksShortIdsIter<'a> {
166    top_blocks: &'a TopBlocks,
167    shards_iter: Option<std::collections::hash_map::Iter<'a, ShardIdent, u32>>,
168}
169
170impl Iterator for TopBlocksShortIdsIter<'_> {
171    type Item = BlockIdShort;
172
173    fn next(&mut self) -> Option<Self::Item> {
174        match &mut self.shards_iter {
175            None => {
176                self.shards_iter = Some(self.top_blocks.shard_heights.0.iter());
177                Some(self.top_blocks.mc_block)
178            }
179            Some(iter) => {
180                let (shard_ident, seqno) = iter.next()?;
181                Some(BlockIdShort::from((*shard_ident, *seqno)))
182            }
183        }
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190
191    #[test]
192    fn test_split_shards() {
193        let mut shard_heights = FastHashMap::default();
194
195        let main_shard = ShardIdent::new_full(0);
196
197        let (left_shard, right_shard) = main_shard.split().unwrap();
198        shard_heights.insert(left_shard, 1000);
199        shard_heights.insert(right_shard, 1001);
200
201        let top_blocks = TopBlocks {
202            mc_block: (ShardIdent::MASTERCHAIN, 100).into(),
203            shard_heights: shard_heights.into(),
204        };
205
206        assert!(!top_blocks.contains(&BlockId {
207            shard: right_shard,
208            seqno: 100,
209            ..Default::default()
210        }));
211
212        // Merged shard test
213        assert!(!top_blocks.contains(&BlockId {
214            shard: main_shard,
215            seqno: 100,
216            ..Default::default()
217        }));
218        assert!(top_blocks.contains(&BlockId {
219            shard: main_shard,
220            seqno: 10000,
221            ..Default::default()
222        }));
223
224        // Split shard test
225        let (right_left_shard, _) = right_shard.split().unwrap();
226        assert!(!top_blocks.contains(&BlockId {
227            shard: right_left_shard,
228            seqno: 100,
229            ..Default::default()
230        }));
231        assert!(top_blocks.contains(&BlockId {
232            shard: right_left_shard,
233            seqno: 10000,
234            ..Default::default()
235        }));
236    }
237}