Skip to main content

tycho_core/storage/block/
package_entry.rs

1use std::cmp::Ordering;
2use std::hash::Hash;
3
4use tycho_block_util::archive::ArchiveEntryType;
5use tycho_storage::kv::{StoredValue, StoredValueBuffer};
6use tycho_types::cell::HashBytes;
7use tycho_types::models::*;
8
9#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy)]
10pub struct PartialBlockId {
11    pub shard: ShardIdent,
12    pub seqno: u32,
13    pub root_hash: HashBytes,
14}
15
16impl PartialBlockId {
17    pub fn as_short_id(&self) -> BlockIdShort {
18        BlockIdShort {
19            shard: self.shard,
20            seqno: self.seqno,
21        }
22    }
23
24    pub fn make_full(&self, file_hash: HashBytes) -> BlockId {
25        BlockId {
26            shard: self.shard,
27            seqno: self.seqno,
28            root_hash: self.root_hash,
29            file_hash,
30        }
31    }
32}
33
34impl From<BlockId> for PartialBlockId {
35    fn from(value: BlockId) -> Self {
36        Self {
37            shard: value.shard,
38            seqno: value.seqno,
39            root_hash: value.root_hash,
40        }
41    }
42}
43
44impl From<&BlockId> for PartialBlockId {
45    fn from(value: &BlockId) -> Self {
46        Self {
47            shard: value.shard,
48            seqno: value.seqno,
49            root_hash: value.root_hash,
50        }
51    }
52}
53
54impl StoredValue for PartialBlockId {
55    const SIZE_HINT: usize = 4 + 8 + 4 + 32;
56
57    type OnStackSlice = [u8; Self::SIZE_HINT];
58
59    fn serialize<T: StoredValueBuffer>(&self, buffer: &mut T) {
60        let mut result = [0; Self::SIZE_HINT];
61        result[..4].copy_from_slice(&self.shard.workchain().to_be_bytes());
62        result[4..12].copy_from_slice(&self.shard.prefix().to_be_bytes());
63        result[12..16].copy_from_slice(&self.seqno.to_be_bytes());
64        result[16..48].copy_from_slice(self.root_hash.as_slice());
65
66        buffer.write_raw_slice(&result);
67    }
68
69    fn deserialize(reader: &mut &[u8]) -> Self
70    where
71        Self: Sized,
72    {
73        assert_eq!(reader.len(), Self::SIZE_HINT, "invalid partial id");
74
75        let workchain = i32::from_be_bytes(reader[..4].try_into().unwrap());
76        let prefix = u64::from_be_bytes(reader[4..12].try_into().unwrap());
77        let seqno = u32::from_be_bytes(reader[12..16].try_into().unwrap());
78        let root_hash = HashBytes::from_slice(&reader[16..48]);
79
80        *reader = &reader[Self::SIZE_HINT..];
81
82        Self {
83            shard: ShardIdent::new(workchain, prefix).expect("invalid shard ident"),
84            seqno,
85            root_hash,
86        }
87    }
88}
89
90/// Package entry id.
91#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
92pub struct PackageEntryKey {
93    pub block_id: PartialBlockId,
94    pub ty: ArchiveEntryType,
95}
96
97impl PackageEntryKey {
98    pub const SIZE_HINT: usize = 4 + 8 + 4 + 32 + 1; // workchain(4) + prefix(8) + seqno(4) + root_hash(32) + ty(1)
99}
100
101impl cassadilia::KeyBytes for PackageEntryKey {
102    type Bytes = [u8; Self::SIZE_HINT];
103
104    fn to_key_bytes(&self) -> Self::Bytes {
105        let mut result = [0u8; Self::SIZE_HINT];
106        result[..4].copy_from_slice(&self.block_id.shard.workchain().to_be_bytes());
107        result[4..12].copy_from_slice(&self.block_id.shard.prefix().to_be_bytes());
108        result[12..16].copy_from_slice(&self.block_id.seqno.to_be_bytes());
109        result[16..48].copy_from_slice(self.block_id.root_hash.as_slice());
110        result[48] = self.ty as u8;
111        result
112    }
113
114    fn from_key_bytes(bytes: &[u8]) -> Option<Self> {
115        if bytes.len() != Self::SIZE_HINT {
116            return None;
117        }
118
119        let workchain = i32::from_be_bytes(bytes[..4].try_into().ok()?);
120        let prefix = u64::from_be_bytes(bytes[4..12].try_into().ok()?);
121        let seqno = u32::from_be_bytes(bytes[12..16].try_into().ok()?);
122        let root_hash = HashBytes::from_slice(&bytes[16..48]);
123        let ty = ArchiveEntryType::from_byte(bytes[48])?;
124
125        Some(Self {
126            block_id: PartialBlockId {
127                shard: ShardIdent::new(workchain, prefix)?,
128                seqno,
129                root_hash,
130            },
131            ty,
132        })
133    }
134}
135
136impl Ord for PackageEntryKey {
137    fn cmp(&self, other: &Self) -> Ordering {
138        // NOTE: Can't just derive Ord here.
139        // RocksDB does a lexicographical byte compare, but the default Ord for
140        // `workchain: i32` is wrong. For RocksDB, the bytes for -1 = u32::MAX, and they are > bytes for 0.
141        // Casting to u32 mimics this byte-level compare.
142        let self_wc_ordered = self.block_id.shard.workchain() as u32;
143        let other_wc_ordered = other.block_id.shard.workchain() as u32;
144
145        (
146            self_wc_ordered,
147            self.block_id.shard.prefix(),
148            self.block_id.seqno,
149            self.block_id.root_hash,
150            self.ty as u8,
151        )
152            .cmp(&(
153                other_wc_ordered,
154                other.block_id.shard.prefix(),
155                other.block_id.seqno,
156                other.block_id.root_hash,
157                other.ty as u8,
158            ))
159    }
160}
161
162impl PartialOrd for PackageEntryKey {
163    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
164        Some(self.cmp(other))
165    }
166}
167
168impl PackageEntryKey {
169    pub fn block(block_id: &BlockId) -> Self {
170        Self {
171            block_id: block_id.into(),
172            ty: ArchiveEntryType::Block,
173        }
174    }
175
176    pub fn proof(block_id: &BlockId) -> Self {
177        Self {
178            block_id: block_id.into(),
179            ty: ArchiveEntryType::Proof,
180        }
181    }
182
183    pub fn queue_diff(block_id: &BlockId) -> Self {
184        Self {
185            block_id: block_id.into(),
186            ty: ArchiveEntryType::QueueDiff,
187        }
188    }
189}
190
191impl From<(BlockId, ArchiveEntryType)> for PackageEntryKey {
192    fn from((block_id, ty): (BlockId, ArchiveEntryType)) -> Self {
193        Self {
194            block_id: block_id.into(),
195            ty,
196        }
197    }
198}
199
200#[cfg(test)]
201mod tests {
202    use std::cmp::Ordering;
203
204    use super::*;
205
206    #[test]
207    fn ord_is_sane_and_matches_bytes() {
208        // copied from remove_blocks
209        let to_bytes = |k: &PackageEntryKey| -> Vec<u8> {
210            let mut bytes = Vec::with_capacity(97);
211            bytes.extend_from_slice(&k.block_id.shard.workchain().to_be_bytes());
212            bytes.extend_from_slice(&k.block_id.shard.prefix().to_be_bytes());
213            bytes.extend_from_slice(&k.block_id.seqno.to_be_bytes());
214            bytes.extend_from_slice(&k.block_id.root_hash.0);
215            bytes.push(k.ty as u8);
216            bytes
217        };
218
219        let base = PackageEntryKey {
220            block_id: PartialBlockId {
221                shard: ShardIdent::BASECHAIN, // wc 0, prefix 0x8000...
222                seqno: 100,
223                root_hash: HashBytes([1; 32]),
224            },
225            ty: ArchiveEntryType::Block,
226        };
227
228        let check = |a: PackageEntryKey, b: PackageEntryKey, expected: Ordering| {
229            let rust_ord = a.cmp(&b);
230            let byte_ord = to_bytes(&a).cmp(&to_bytes(&b));
231            // if these don't match, our Ord is fucked
232            assert_eq!(rust_ord, byte_ord, "Rust Ord vs byte Ord mismatch");
233            // check the actual ordering logic
234            assert_eq!(rust_ord, expected, "Unexpected ordering");
235        };
236
237        // --- test cases ---
238
239        // masterchain (-1) > basechain (0)
240        check(
241            PackageEntryKey {
242                block_id: PartialBlockId {
243                    shard: ShardIdent::MASTERCHAIN,
244                    ..base.block_id
245                },
246                ..base
247            },
248            base,
249            Ordering::Greater,
250        );
251
252        // shard prefix split: left < right
253        let (left_shard, right_shard) = base.block_id.shard.split().unwrap();
254        check(
255            PackageEntryKey {
256                block_id: PartialBlockId {
257                    shard: left_shard,
258                    ..base.block_id
259                },
260                ..base
261            },
262            PackageEntryKey {
263                block_id: PartialBlockId {
264                    shard: right_shard,
265                    ..base.block_id
266                },
267                ..base
268            },
269            Ordering::Less,
270        );
271
272        // seqno
273        check(
274            base,
275            PackageEntryKey {
276                block_id: PartialBlockId {
277                    seqno: base.block_id.seqno + 1,
278                    ..base.block_id
279                },
280                ..base
281            },
282            Ordering::Less,
283        );
284
285        // root hash
286        let mut bigger_hash = base.block_id.root_hash;
287        bigger_hash.0[31] = 2; // just need a lexicographically larger hash
288        check(
289            base,
290            PackageEntryKey {
291                block_id: PartialBlockId {
292                    root_hash: bigger_hash,
293                    ..base.block_id
294                },
295                ..base
296            },
297            Ordering::Less,
298        );
299
300        // entry type
301        check(
302            base,
303            PackageEntryKey {
304                ty: ArchiveEntryType::Proof,
305                ..base
306            },
307            Ordering::Less,
308        );
309    }
310}