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#[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 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 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 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 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
546pub struct AlwaysInclude;
548
549impl MerkleFilter for AlwaysInclude {
550 fn check(&self, _: &HashBytes) -> FilterAction {
551 FilterAction::Include
552 }
553}