Skip to main content

tycho_block_util/block/
block_proof_stuff.rs

1use std::sync::Arc;
2
3use anyhow::Result;
4use tycho_types::merkle::*;
5use tycho_types::models::*;
6use tycho_types::prelude::*;
7
8use crate::archive::WithArchiveData;
9use crate::state::ShardStateStuff;
10
11pub type BlockProofStuffAug = WithArchiveData<BlockProofStuff>;
12
13/// Deserialized block proof.
14#[derive(Clone)]
15#[repr(transparent)]
16pub struct BlockProofStuff {
17    inner: Arc<Inner>,
18}
19
20impl BlockProofStuff {
21    #[cfg(any(test, feature = "test"))]
22    pub fn new_empty(block_id: &BlockId) -> Self {
23        use tycho_types::cell::Lazy;
24
25        let block_info = BlockInfo {
26            shard: block_id.shard,
27            seqno: block_id.seqno,
28            ..Default::default()
29        };
30
31        let block = Block {
32            global_id: 0,
33            info: Lazy::new(&block_info).unwrap(),
34            value_flow: Lazy::new(&ValueFlow::default()).unwrap(),
35            state_update: Lazy::new(&MerkleUpdate::default()).unwrap(),
36            out_msg_queue_updates: OutMsgQueueUpdates {
37                diff_hash: Default::default(),
38                tail_len: 0,
39            },
40            extra: Lazy::new(&BlockExtra::default()).unwrap(),
41        };
42
43        let root = CellBuilder::build_from(&block).unwrap();
44        let root = MerkleProofBuilder::new(root.as_ref(), AlwaysInclude)
45            .build()
46            .unwrap();
47
48        Self::from_proof(Box::new(BlockProof {
49            proof_for: *block_id,
50            root: CellBuilder::build_from(root).unwrap(),
51            signatures: block_id.is_masterchain().then_some(BlockSignatures {
52                validator_info: ValidatorBaseInfo {
53                    validator_list_hash_short: 0,
54                    catchain_seqno: 0,
55                },
56                consensus_info: ConsensusInfo {
57                    vset_switch_round: 0,
58                    prev_vset_switch_round: 0,
59                    genesis_info: GenesisInfo::default(),
60                    prev_shuffle_mc_validators: true,
61                },
62                signature_count: 0,
63                total_weight: 0,
64                signatures: Dict::new(),
65            }),
66        }))
67    }
68
69    pub fn from_proof(proof: Box<BlockProof>) -> Self {
70        Self {
71            inner: Arc::new(Inner { proof }),
72        }
73    }
74
75    pub fn deserialize(block_id: &BlockId, data: &[u8]) -> Result<Self> {
76        let proof = BocRepr::decode::<Box<BlockProof>, _>(data)?;
77
78        anyhow::ensure!(
79            &proof.proof_for == block_id,
80            "proof block id mismatch (found: {})",
81            proof.proof_for,
82        );
83
84        Ok(Self {
85            inner: Arc::new(Inner { proof }),
86        })
87    }
88
89    pub fn with_archive_data(self, data: Vec<u8>) -> WithArchiveData<Self> {
90        WithArchiveData::new(self, data)
91    }
92
93    pub fn id(&self) -> &BlockId {
94        &self.inner.proof.proof_for
95    }
96
97    pub fn proof(&self) -> &BlockProof {
98        &self.inner.proof
99    }
100
101    pub fn is_link(&self) -> bool {
102        !self.inner.proof.proof_for.is_masterchain()
103    }
104
105    pub fn virtualize_block_root(&self) -> Result<&DynCell> {
106        let merkle_proof = self.inner.proof.root.parse_exotic::<MerkleProofRef<'_>>()?;
107        let block_virt_root = merkle_proof.cell.virtualize();
108
109        anyhow::ensure!(
110            &self.inner.proof.proof_for.root_hash == block_virt_root.repr_hash(),
111            "merkle proof has invalid virtual hash (found: {}, expected: {})",
112            block_virt_root.repr_hash(),
113            self.inner.proof.proof_for
114        );
115
116        Ok(block_virt_root)
117    }
118
119    pub fn virtualize_block(&self) -> Result<(Block, HashBytes)> {
120        let cell = self.virtualize_block_root()?;
121        let hash = cell.repr_hash();
122        Ok((cell.parse::<Block>()?, *hash))
123    }
124
125    pub fn check_with_prev_key_block_proof(
126        &self,
127        prev_key_block_proof: &BlockProofStuff,
128    ) -> Result<()> {
129        let (virt_block, virt_block_info) = self.pre_check_block_proof()?;
130        check_with_prev_key_block_proof(self, prev_key_block_proof, &virt_block, &virt_block_info)?;
131        Ok(())
132    }
133
134    pub fn check_with_master_state(&self, master_state: &ShardStateStuff) -> Result<()> {
135        anyhow::ensure!(
136            !self.is_link(),
137            "cannot check proof link using master state"
138        );
139
140        let (virt_block, virt_block_info) = self.pre_check_block_proof()?;
141        check_with_master_state(self, master_state, &virt_block, &virt_block_info)?;
142        Ok(())
143    }
144
145    pub fn check_proof_link(&self) -> Result<()> {
146        anyhow::ensure!(self.is_link(), "cannot check full proof as link");
147
148        self.pre_check_block_proof()?;
149        Ok(())
150    }
151
152    pub fn pre_check_block_proof(&self) -> Result<(Block, BlockInfo)> {
153        let block_id = self.id();
154        anyhow::ensure!(
155            block_id.is_masterchain() || self.inner.proof.signatures.is_none(),
156            "proof for non-masterchain block must not contain signatures",
157        );
158
159        let (virt_block, virt_block_hash) = self.virtualize_block()?;
160        anyhow::ensure!(
161            virt_block_hash == block_id.root_hash,
162            "proof contains an invalid block (found: {virt_block_hash}, expected: {})",
163            block_id.root_hash,
164        );
165
166        #[expect(
167            clippy::disallowed_methods,
168            reason = "We are working with a virtual block here, so `load_info` and other methods are necessary"
169        )]
170        let info = virt_block.load_info()?;
171        let _value_flow = virt_block.load_value_flow()?;
172        let _state_update = virt_block.load_state_update()?;
173
174        anyhow::ensure!(
175            info.version == 0,
176            "proof has an unsupported block structure version: {}",
177            info.version,
178        );
179
180        anyhow::ensure!(
181            info.seqno == block_id.seqno,
182            "proof has an incorrect block seqno (found: {}, expected: {})",
183            info.seqno,
184            block_id.seqno,
185        );
186
187        anyhow::ensure!(
188            info.shard == block_id.shard,
189            "proof has an incorrect block shard (found: {}, expected: {})",
190            info.shard,
191            block_id.shard,
192        );
193
194        anyhow::ensure!(
195            info.load_master_ref()?.is_none() == info.shard.is_masterchain(),
196            "proof has an invalid `not_master` flag in block info",
197        );
198
199        anyhow::ensure!(
200            !block_id.is_masterchain()
201                || !info.after_merge && !info.before_split && !info.after_split,
202            "proof has incorrect split/merge flags in block info",
203        );
204
205        anyhow::ensure!(
206            !info.after_merge || !info.after_split,
207            "proof has both split and merge flags in block info",
208        );
209
210        anyhow::ensure!(
211            !info.after_split || !info.shard.is_full(),
212            "proof has `after_split` flag set for a full shard in block info",
213        );
214
215        anyhow::ensure!(
216            !info.after_merge || info.shard.can_split(),
217            "proof has `after_merge` flag set for the tiniest shard in block info",
218        );
219
220        anyhow::ensure!(
221            !info.key_block || block_id.is_masterchain(),
222            "proof has `key_block` flag set for a non-masterchain block in block info",
223        );
224
225        Ok((virt_block, info))
226    }
227
228    fn check_consensus_info(&self, mc_consensus_info: &ConsensusInfo) -> Result<()> {
229        let Some(signatures) = self.inner.proof.signatures.as_ref() else {
230            anyhow::bail!("proof doesn't have signatures to check");
231        };
232
233        anyhow::ensure!(
234            &signatures.consensus_info == mc_consensus_info,
235            "block consensus info does not match master state consensus info (found: {:?}, expected: {:?}",
236            signatures.consensus_info,
237            mc_consensus_info,
238        );
239
240        Ok(())
241    }
242
243    fn check_signatures(&self, subset: &ValidatorSubsetInfo) -> Result<()> {
244        // Prepare
245        let Some(signatures) = self.inner.proof.signatures.as_ref() else {
246            anyhow::bail!("proof doesn't have signatures to check");
247        };
248
249        anyhow::ensure!(
250            signatures.validator_info.validator_list_hash_short == subset.short_hash,
251            "proof contains an invalid validator set hash (found: {}, expected: {})",
252            signatures.validator_info.validator_list_hash_short,
253            subset.short_hash,
254        );
255
256        let expected_count = signatures.signature_count as usize;
257
258        {
259            let mut count = 0usize;
260            for value in signatures.signatures.raw_values() {
261                value?;
262                count += 1;
263                if count > expected_count {
264                    break;
265                }
266            }
267
268            anyhow::ensure!(
269                expected_count == count,
270                "proof contains an invalid signature count (found: {count}, expected: {expected_count})",
271            );
272        }
273
274        // Check signatures
275        let checked_data = Block::build_data_for_sign(self.id());
276
277        let mut total_weight = 0u64;
278        for v in subset.validators.iter() {
279            match total_weight.checked_add(v.weight) {
280                Some(new_total_weight) => total_weight = new_total_weight,
281                None => anyhow::bail!("overflow while computing total weight of validators subset"),
282            }
283        }
284
285        let weight = match signatures
286            .signatures
287            .check_signatures(&subset.validators, &checked_data)
288        {
289            Ok(weight) => weight,
290            Err(e) => anyhow::bail!("proof contains invalid signatures: {e:?}"),
291        };
292
293        // Check weight
294        anyhow::ensure!(
295            weight == signatures.total_weight,
296            "total signature weight mismatch (found: {weight}, expected: {})",
297            signatures.total_weight
298        );
299
300        match (weight.checked_mul(3), total_weight.checked_mul(2)) {
301            (Some(weight_x3), Some(total_weight_x2)) => {
302                anyhow::ensure!(
303                    weight_x3 > total_weight_x2,
304                    "proof contains too small signatures weight"
305                );
306            }
307            _ => anyhow::bail!("overflow while checking signature weight"),
308        }
309
310        Ok(())
311    }
312
313    fn process_given_state(
314        &self,
315        master_state: &ShardStateStuff,
316        block_info: &BlockInfo,
317    ) -> Result<ValidatorSubsetInfo> {
318        anyhow::ensure!(
319            master_state.block_id().is_masterchain(),
320            "state for {} doesn't belong to the masterchain",
321            master_state.block_id(),
322        );
323
324        anyhow::ensure!(
325            self.id().is_masterchain(),
326            "cannot check proof for a non-masterchain block using master state",
327        );
328
329        anyhow::ensure!(
330            block_info.prev_key_block_seqno <= master_state.block_id().seqno,
331            "cannot check proof using the master state {}, because it is older than the previous key block with seqno {}",
332            master_state.block_id(),
333            block_info.prev_key_block_seqno,
334        );
335
336        anyhow::ensure!(
337            master_state.block_id().seqno < self.id().seqno,
338            "cannot check proof using a newer master state {}",
339            master_state.block_id(),
340        );
341
342        let (validator_set, shuffle_validators) = {
343            let Some(custom) = master_state.state().load_custom()? else {
344                anyhow::bail!("no additional masterchain data found in the master state");
345            };
346            let validator_set = custom.config.get_current_validator_set()?;
347            let shuffle_validators = custom.config.get_collation_config()?.shuffle_mc_validators;
348            (validator_set, shuffle_validators)
349        };
350
351        self.calc_validators_subset_standard(&validator_set, shuffle_validators)
352    }
353
354    fn process_prev_key_block_proof(
355        &self,
356        prev_key_block_proof: &BlockProofStuff,
357    ) -> Result<ValidatorSubsetInfo> {
358        let (virt_key_block, prev_key_block_info) = prev_key_block_proof.pre_check_block_proof()?;
359
360        anyhow::ensure!(
361            prev_key_block_info.key_block,
362            "expected a proof for a key block",
363        );
364
365        #[expect(
366            clippy::disallowed_methods,
367            reason = "We are working with a virtual block here, so `load_extra` and other methods are necessary"
368        )]
369        let (validator_set, shuffle_validators) = {
370            let extra = virt_key_block.load_extra()?;
371            let Some(custom) = extra.load_custom()? else {
372                anyhow::bail!("no additional masterchain data found in the key block");
373            };
374            let Some(config) = custom.config.as_ref() else {
375                anyhow::bail!("no config found in the key block");
376            };
377
378            let validator_set = config.get_current_validator_set()?;
379            let shuffle_validators = config.get_collation_config()?.shuffle_mc_validators;
380            (validator_set, shuffle_validators)
381        };
382
383        self.calc_validators_subset_standard(&validator_set, shuffle_validators)
384    }
385
386    fn calc_validators_subset_standard(
387        &self,
388        validator_set: &ValidatorSet,
389        shuffle_validators: bool,
390    ) -> Result<ValidatorSubsetInfo> {
391        let cc_seqno = self
392            .inner
393            .proof
394            .signatures
395            .as_ref()
396            .map(|s| s.validator_info.catchain_seqno)
397            .unwrap_or_default();
398
399        ValidatorSubsetInfo::compute_standard(validator_set, cc_seqno, shuffle_validators)
400    }
401}
402
403impl AsRef<BlockProof> for BlockProofStuff {
404    #[inline]
405    fn as_ref(&self) -> &BlockProof {
406        &self.inner.proof
407    }
408}
409
410unsafe impl arc_swap::RefCnt for BlockProofStuff {
411    type Base = Inner;
412
413    fn into_ptr(me: Self) -> *mut Self::Base {
414        arc_swap::RefCnt::into_ptr(me.inner)
415    }
416
417    fn as_ptr(me: &Self) -> *mut Self::Base {
418        arc_swap::RefCnt::as_ptr(&me.inner)
419    }
420
421    unsafe fn from_ptr(ptr: *const Self::Base) -> Self {
422        Self {
423            inner: unsafe { arc_swap::RefCnt::from_ptr(ptr) },
424        }
425    }
426}
427
428#[doc(hidden)]
429pub struct Inner {
430    proof: Box<BlockProof>,
431}
432
433pub fn check_with_prev_key_block_proof(
434    proof: &BlockProofStuff,
435    prev_key_block_proof: &BlockProofStuff,
436    virt_block: &Block,
437    virt_block_info: &BlockInfo,
438) -> Result<()> {
439    let proof_id = proof.id();
440    let key_block_id = prev_key_block_proof.id();
441
442    anyhow::ensure!(
443        proof_id.is_masterchain(),
444        "cannot check a non-masterchain block using the previous key block",
445    );
446
447    anyhow::ensure!(
448        key_block_id.is_masterchain(),
449        "given previous key block is not a masterchain block (id: {key_block_id})",
450    );
451
452    let prev_key_block_seqno = virt_block_info.prev_key_block_seqno;
453    anyhow::ensure!(
454        key_block_id.seqno == prev_key_block_seqno,
455        "given previous key block is not the one expected \
456        (found: {key_block_id}, expected seqno: {prev_key_block_seqno})",
457    );
458
459    anyhow::ensure!(
460        key_block_id.seqno < proof_id.seqno,
461        "given previous key block has a greater seqno than the proof block ({} >= {})",
462        key_block_id.seqno,
463        proof_id.seqno,
464    );
465
466    let subset = proof.process_prev_key_block_proof(prev_key_block_proof)?;
467
468    if virt_block_info.key_block {
469        pre_check_key_block_proof(virt_block)?;
470    }
471
472    proof.check_signatures(&subset)
473}
474
475pub fn check_with_master_state(
476    proof: &BlockProofStuff,
477    master_state: &ShardStateStuff,
478    virt_block: &Block,
479    virt_block_info: &BlockInfo,
480) -> Result<()> {
481    if virt_block_info.key_block {
482        pre_check_key_block_proof(virt_block)?;
483    } else {
484        proof.check_consensus_info(&master_state.state_extra()?.consensus_info)?;
485    }
486
487    let subset = proof.process_given_state(master_state, virt_block_info)?;
488    proof.check_signatures(&subset)
489}
490
491#[expect(
492    clippy::disallowed_methods,
493    reason = "We are working with a virtual block here, so `load_extra` and other methods are necessary"
494)]
495fn pre_check_key_block_proof(virt_block: &Block) -> Result<()> {
496    let extra = virt_block.load_extra()?;
497    let Some(mc_extra) = extra.load_custom()? else {
498        anyhow::bail!("proof contains a virtual block without masterchain block extra")
499    };
500
501    let Some(config) = mc_extra.config.as_ref() else {
502        anyhow::bail!("proof contains a virtual block without config params")
503    };
504
505    if config.get::<ConfigParam34>()?.is_none() {
506        anyhow::bail!("proof contains a virtual block without current validators config param");
507    }
508
509    for param in 32..=38 {
510        if let Some(mut vset) = config.get_raw(param)? {
511            ValidatorSet::load_from(&mut vset)?;
512        }
513    }
514
515    // TODO: remove this once the new consensus is implemented
516    let _catchain_config = config.get::<ConfigParam28>()?;
517
518    Ok(())
519}
520
521#[derive(Clone, Debug)]
522pub struct ValidatorSubsetInfo {
523    pub validators: Vec<ValidatorDescription>,
524    pub short_hash: u32,
525}
526
527impl ValidatorSubsetInfo {
528    pub fn compute_standard(
529        validator_set: &ValidatorSet,
530        cc_seqno: u32,
531        shuffle_validators: bool,
532    ) -> Result<Self> {
533        let Some((validators, short_hash)) =
534            validator_set.compute_mc_subset(cc_seqno, shuffle_validators)
535        else {
536            anyhow::bail!("failed to compute a validator subset");
537        };
538
539        Ok(ValidatorSubsetInfo {
540            validators,
541            short_hash,
542        })
543    }
544}
545
546// TODO: Move into `types`.
547pub struct AlwaysInclude;
548
549impl MerkleFilter for AlwaysInclude {
550    fn check(&self, _: &HashBytes) -> FilterAction {
551        FilterAction::Include
552    }
553}