1use std::time::{SystemTime, UNIX_EPOCH};
5
6use crate::blocks::{BLOCK_MESSAGE_LIMIT, Block, FullTipset, GossipBlock, Tipset, TxMeta};
7use crate::chain::ChainStore;
8use crate::message::SignedMessage;
9use crate::shim::clock::ChainEpoch;
10use crate::shim::message::Message;
11use crate::utils::{cid::CidCborExt, db::CborStoreExt};
12use cid::Cid;
13use fil_actors_shared::fvm_ipld_amt::{Amtv0 as Amt, Error as IpldAmtError};
14use fvm_ipld_blockstore::Blockstore;
15use fvm_ipld_encoding::Error as EncodingError;
16use thiserror::Error;
17
18use crate::chain_sync::bad_block_cache::{BadBlockCache, SeenBlockCache};
19
20const MAX_HEIGHT_DRIFT: ChainEpoch = 5;
21
22fn max_allowed_epoch(
26 now_secs: u64,
27 genesis_timestamp: u64,
28 block_delay: u32,
29) -> Option<ChainEpoch> {
30 let elapsed = now_secs.checked_sub(genesis_timestamp)?;
31 let delay = u64::from(block_delay);
32 if delay == 0 {
33 return None;
34 }
35 let epoch = ChainEpoch::try_from(elapsed / delay).unwrap_or(ChainEpoch::MAX);
36 Some(epoch.saturating_add(MAX_HEIGHT_DRIFT))
37}
38
39fn now_secs() -> u64 {
40 SystemTime::now()
41 .duration_since(UNIX_EPOCH)
42 .unwrap_or_default()
43 .as_secs()
44}
45
46#[derive(Debug, Error)]
47pub enum TipsetValidationError {
48 #[error("Tipset has no blocks")]
49 NoBlocks,
50 #[error("Tipset has an epoch that is too large")]
51 EpochTooLarge,
52 #[error("Tipset has an insufficient weight")]
53 InsufficientWeight,
54 #[error("Tipset block = [CID = {0}] is invalid")]
55 InvalidBlock(Cid),
56 #[error("Tipset headers are invalid")]
57 InvalidRoots,
58 #[error("Tipset IPLD error: {0}")]
59 IpldAmt(String),
60 #[error("Block store error while validating tipset: {0}")]
61 Blockstore(String),
62 #[error("Encoding error while validating tipset: {0}")]
63 Encoding(EncodingError),
64}
65
66impl From<EncodingError> for TipsetValidationError {
67 fn from(err: EncodingError) -> Self {
68 TipsetValidationError::Encoding(err)
69 }
70}
71
72impl From<IpldAmtError> for TipsetValidationError {
73 fn from(err: IpldAmtError) -> Self {
74 TipsetValidationError::IpldAmt(err.to_string())
75 }
76}
77
78pub struct TipsetValidator<'a>(pub &'a FullTipset);
79
80impl TipsetValidator<'_> {
81 pub fn validate(
82 &self,
83 chainstore: &ChainStore,
84 bad_block_cache: Option<&BadBlockCache>,
85 genesis_tipset: &Tipset,
86 block_delay: u32,
87 ) -> Result<(), TipsetValidationError> {
88 if self.0.blocks().is_empty() {
90 return Err(TipsetValidationError::NoBlocks);
91 }
92
93 self.validate_epoch(genesis_tipset, block_delay)?;
95
96 for block in self.0.blocks() {
101 Self::validate_msg_root(chainstore.db(), block)?;
102 if let Some(bad_block_cache) = bad_block_cache
103 && bad_block_cache.get(block.cid()).is_some()
104 {
105 return Err(TipsetValidationError::InvalidBlock(*block.cid()));
106 }
107 }
108
109 Ok(())
110 }
111
112 pub fn validate_epoch(
113 &self,
114 genesis_tipset: &Tipset,
115 block_delay: u32,
116 ) -> Result<(), TipsetValidationError> {
117 let max = max_allowed_epoch(now_secs(), genesis_tipset.min_timestamp(), block_delay)
118 .unwrap_or(ChainEpoch::MAX);
119 if self.0.epoch() > max {
120 Err(TipsetValidationError::EpochTooLarge)
121 } else {
122 Ok(())
123 }
124 }
125
126 pub fn validate_msg_root<DB: Blockstore>(
127 blockstore: &DB,
128 block: &Block,
129 ) -> Result<(), TipsetValidationError> {
130 let msg_root = Self::compute_msg_root(blockstore, block.bls_msgs(), block.secp_msgs())?;
131 if block.header().messages != msg_root {
132 Err(TipsetValidationError::InvalidRoots)
133 } else {
134 Ok(())
135 }
136 }
137
138 pub fn compute_msg_root<DB: Blockstore>(
139 blockstore: &DB,
140 bls_msgs: &[Message],
141 secp_msgs: &[SignedMessage],
142 ) -> Result<Cid, TipsetValidationError> {
143 let bls_cids = bls_msgs
145 .iter()
146 .map(Cid::from_cbor_blake2b256)
147 .collect::<Result<Vec<Cid>, fvm_ipld_encoding::Error>>()?;
148 let secp_cids = secp_msgs
149 .iter()
150 .map(Cid::from_cbor_blake2b256)
151 .collect::<Result<Vec<Cid>, fvm_ipld_encoding::Error>>()?;
152
153 let bls_message_root = Amt::new_from_iter(blockstore, bls_cids)?;
155 let secp_message_root = Amt::new_from_iter(blockstore, secp_cids)?;
156 let meta = TxMeta {
157 bls_message_root,
158 secp_message_root,
159 };
160
161 blockstore
163 .put_cbor_default(&meta)
164 .map_err(|e| TipsetValidationError::Blockstore(e.to_string()))
165 }
166}
167
168#[derive(Debug, Error)]
169pub enum GossipBlockRejectReason {
170 #[error("block epoch {0} is too far in the future")]
171 EpochTooFarAhead(ChainEpoch),
172 #[error("block epoch {epoch} is beyond finality (finalized: {finalized_epoch})")]
173 EpochBeyondFinality {
174 epoch: ChainEpoch,
175 finalized_epoch: ChainEpoch,
176 },
177 #[error("block epoch {0} is negative")]
178 NegativeEpoch(ChainEpoch),
179 #[error("block timestamp {timestamp} inconsistent with epoch {epoch} (expected {expected})")]
180 TimestampMismatch {
181 timestamp: u64,
182 epoch: ChainEpoch,
183 expected: u64,
184 },
185 #[error("block has no signature")]
186 MissingSignature,
187 #[error("block has no election proof")]
188 MissingElectionProof,
189 #[error("block election proof has win_count {0} < 1")]
190 InvalidWinCount(i64),
191 #[error("block has {0} messages, exceeding limit of {BLOCK_MESSAGE_LIMIT}")]
192 TooManyMessages(usize),
193 #[error("block CID {0} is in bad block cache")]
194 BadBlock(Cid),
195 #[error("duplicate block CID {0}")]
196 DuplicateBlock(Cid),
197}
198
199impl GossipBlockRejectReason {
200 pub fn label(&self) -> &'static str {
201 match self {
202 Self::EpochTooFarAhead(_) => "epoch_too_far_ahead",
203 Self::EpochBeyondFinality { .. } => "epoch_beyond_finality",
204 Self::NegativeEpoch(_) => "negative_epoch",
205 Self::TimestampMismatch { .. } => "timestamp_mismatch",
206 Self::MissingSignature => "missing_signature",
207 Self::MissingElectionProof => "missing_election_proof",
208 Self::InvalidWinCount(_) => "invalid_win_count",
209 Self::TooManyMessages(_) => "too_many_messages",
210 Self::BadBlock(_) => "bad_block",
211 Self::DuplicateBlock(_) => "duplicate_block",
212 }
213 }
214}
215
216pub struct GossipBlockValidator<'a> {
220 block: &'a GossipBlock,
221}
222
223impl<'a> GossipBlockValidator<'a> {
224 pub fn new(block: &'a GossipBlock) -> Self {
225 Self { block }
226 }
227
228 pub fn validate_pre_fetch(
231 &self,
232 genesis_tipset: &Tipset,
233 block_delay: u32,
234 finalized_epoch: ChainEpoch,
235 bad_block_cache: Option<&BadBlockCache>,
236 seen_block_cache: &SeenBlockCache,
237 ) -> Result<(), GossipBlockRejectReason> {
238 let cid = *self.block.header.cid();
239 Self::check_bad_block_cache(cid, bad_block_cache)?;
240 self.validate_epoch_range(genesis_tipset, block_delay, finalized_epoch)?;
241 self.validate_timestamp(genesis_tipset, block_delay)?;
242 self.validate_election_proof()?;
243 self.validate_signature_present()?;
244 self.validate_message_count()?;
245 Self::check_duplicate(cid, seen_block_cache)?;
248 Ok(())
249 }
250
251 fn check_duplicate(
252 cid: Cid,
253 seen_block_cache: &SeenBlockCache,
254 ) -> Result<(), GossipBlockRejectReason> {
255 if seen_block_cache.test_and_insert(&cid) {
256 return Err(GossipBlockRejectReason::DuplicateBlock(cid));
257 }
258 Ok(())
259 }
260
261 fn check_bad_block_cache(
262 cid: Cid,
263 bad_block_cache: Option<&BadBlockCache>,
264 ) -> Result<(), GossipBlockRejectReason> {
265 if let Some(cache) = bad_block_cache
266 && cache.get(&cid).is_some()
267 {
268 return Err(GossipBlockRejectReason::BadBlock(cid));
269 }
270 Ok(())
271 }
272
273 fn validate_epoch_range(
274 &self,
275 genesis_tipset: &Tipset,
276 block_delay: u32,
277 finalized_epoch: ChainEpoch,
278 ) -> Result<(), GossipBlockRejectReason> {
279 let epoch = self.block.header.epoch;
280 if epoch < 0 {
281 return Err(GossipBlockRejectReason::NegativeEpoch(epoch));
282 }
283 let max = max_allowed_epoch(now_secs(), genesis_tipset.min_timestamp(), block_delay)
284 .unwrap_or(ChainEpoch::MAX);
285 if epoch > max {
286 return Err(GossipBlockRejectReason::EpochTooFarAhead(epoch));
287 }
288 if epoch < finalized_epoch {
289 return Err(GossipBlockRejectReason::EpochBeyondFinality {
290 epoch,
291 finalized_epoch,
292 });
293 }
294 Ok(())
295 }
296
297 fn validate_timestamp(
300 &self,
301 genesis_tipset: &Tipset,
302 block_delay: u32,
303 ) -> Result<(), GossipBlockRejectReason> {
304 let epoch = self.block.header.epoch;
305 let timestamp = self.block.header.timestamp;
306 let expected = (epoch as u64)
309 .checked_mul(u64::from(block_delay))
310 .and_then(|elapsed| genesis_tipset.min_timestamp().checked_add(elapsed))
311 .ok_or(GossipBlockRejectReason::EpochTooFarAhead(epoch))?;
312 if timestamp != expected {
313 return Err(GossipBlockRejectReason::TimestampMismatch {
314 timestamp,
315 epoch,
316 expected,
317 });
318 }
319 Ok(())
320 }
321
322 fn validate_election_proof(&self) -> Result<(), GossipBlockRejectReason> {
323 match &self.block.header.election_proof {
324 None => Err(GossipBlockRejectReason::MissingElectionProof),
325 Some(proof) if proof.win_count < 1 => {
326 Err(GossipBlockRejectReason::InvalidWinCount(proof.win_count))
327 }
328 _ => Ok(()),
329 }
330 }
331
332 fn validate_signature_present(&self) -> Result<(), GossipBlockRejectReason> {
333 if self.block.header.signature.is_none() {
334 return Err(GossipBlockRejectReason::MissingSignature);
335 }
336 Ok(())
337 }
338
339 fn validate_message_count(&self) -> Result<(), GossipBlockRejectReason> {
340 let count = self.block.bls_messages.len() + self.block.secpk_messages.len();
341 if count > BLOCK_MESSAGE_LIMIT {
342 return Err(GossipBlockRejectReason::TooManyMessages(count));
343 }
344 Ok(())
345 }
346}
347
348#[cfg(test)]
349mod tests {
350 use std::convert::TryFrom;
351
352 use crate::blocks::{CachingBlockHeader, ElectionProof, GossipBlock, RawBlockHeader, Tipset};
353 use crate::chain_sync::bad_block_cache::{BadBlockCache, SeenBlockCache};
354 use crate::db::MemoryDB;
355 use crate::message::SignedMessage;
356 use crate::shim::crypto::{Signature, SignatureType};
357 use crate::shim::message::Message;
358 use crate::test_utils::construct_messages;
359 use crate::utils::encoding::from_slice_with_fallback;
360 use base64::{Engine, prelude::BASE64_STANDARD};
361 use cid::Cid;
362
363 use super::{GossipBlockRejectReason, GossipBlockValidator, TipsetValidator};
364
365 #[test]
366 fn compute_msg_meta_given_msgs_test() {
367 let blockstore = MemoryDB::default();
368
369 let (bls, secp) = construct_messages();
370
371 let expected_root =
372 Cid::try_from("bafy2bzaceasssikoiintnok7f3sgnekfifarzobyr3r4f25sgxmn23q4c35ic")
373 .unwrap();
374
375 let root = TipsetValidator::compute_msg_root(&blockstore, &[bls], &[secp])
376 .expect("Computing message root should succeed");
377 assert_eq!(root, expected_root);
378 }
379
380 #[test]
381 fn empty_msg_meta_vector() {
382 let blockstore = MemoryDB::default();
383 let usm: Vec<Message> =
384 from_slice_with_fallback(&BASE64_STANDARD.decode("gA==").unwrap()).unwrap();
385 let sm: Vec<SignedMessage> =
386 from_slice_with_fallback(&BASE64_STANDARD.decode("gA==").unwrap()).unwrap();
387
388 assert_eq!(
389 TipsetValidator::compute_msg_root(&blockstore, &usm, &sm)
390 .expect("Computing message root should succeed")
391 .to_string(),
392 "bafy2bzacecmda75ovposbdateg7eyhwij65zklgyijgcjwynlklmqazpwlhba"
393 );
394 }
395
396 #[test]
397 fn max_allowed_epoch_basic() {
398 assert_eq!(super::max_allowed_epoch(1300, 1000, 30), Some(15));
401 }
402
403 #[test]
404 fn max_allowed_epoch_at_genesis() {
405 assert_eq!(super::max_allowed_epoch(1000, 1000, 30), Some(5));
407 }
408
409 #[test]
410 fn max_allowed_epoch_clock_before_genesis() {
411 assert_eq!(super::max_allowed_epoch(500, 1000, 30), None);
413 }
414
415 #[test]
416 fn max_allowed_epoch_zero_block_delay() {
417 assert_eq!(super::max_allowed_epoch(2000, 1000, 0), None);
419 }
420
421 fn make_gossip_block_with(f: impl FnOnce(&mut RawBlockHeader)) -> GossipBlock {
422 let mut raw = RawBlockHeader {
423 election_proof: Some(ElectionProof {
424 win_count: 1,
425 vrfproof: Default::default(),
426 }),
427 signature: Some(Signature {
428 sig_type: SignatureType::Bls,
429 bytes: vec![0u8; 96],
430 }),
431 ..Default::default()
432 };
433 f(&mut raw);
434 GossipBlock {
435 header: CachingBlockHeader::from(raw),
436 bls_messages: vec![],
437 secpk_messages: vec![],
438 }
439 }
440
441 fn make_valid_gossip_block() -> GossipBlock {
442 make_gossip_block_with(|_| {})
443 }
444
445 fn make_genesis() -> Tipset {
446 Tipset::from(CachingBlockHeader::default())
447 }
448
449 #[test]
450 fn gossip_block_validator_accepts_valid_block() {
451 let block = make_valid_gossip_block();
452 let genesis = make_genesis();
453 let seen = SeenBlockCache::default();
454
455 let result = GossipBlockValidator::new(&block).validate_pre_fetch(
456 &genesis, 30, 0, None, &seen,
460 );
461 assert!(result.is_ok());
462 }
463
464 #[test]
465 fn gossip_block_validator_rejects_duplicate() {
466 let block = make_valid_gossip_block();
467 let genesis = make_genesis();
468 let seen = SeenBlockCache::default();
469
470 assert!(
471 GossipBlockValidator::new(&block)
472 .validate_pre_fetch(&genesis, 30, 0, None, &seen)
473 .is_ok()
474 );
475
476 let err = GossipBlockValidator::new(&block)
477 .validate_pre_fetch(&genesis, 30, 0, None, &seen)
478 .unwrap_err();
479 assert!(matches!(err, GossipBlockRejectReason::DuplicateBlock(_)));
480 }
481
482 #[test]
483 fn gossip_block_validator_rejects_bad_block() {
484 let block = make_valid_gossip_block();
485 let genesis = make_genesis();
486 let seen = SeenBlockCache::default();
487 let bad_cache = BadBlockCache::default();
488 bad_cache.push(*block.header.cid());
489
490 let err = GossipBlockValidator::new(&block)
491 .validate_pre_fetch(&genesis, 30, 0, Some(&bad_cache), &seen)
492 .unwrap_err();
493 assert!(matches!(err, GossipBlockRejectReason::BadBlock(_)));
494 }
495
496 #[test]
497 fn gossip_block_validator_rejects_epoch_too_far_ahead() {
498 let block = make_gossip_block_with(|h| h.epoch = i64::MAX);
499 let genesis = make_genesis();
500 let seen = SeenBlockCache::default();
501
502 let err = GossipBlockValidator::new(&block)
503 .validate_pre_fetch(&genesis, 30, 0, None, &seen)
504 .unwrap_err();
505 assert!(matches!(err, GossipBlockRejectReason::EpochTooFarAhead(_)));
506 }
507
508 #[test]
509 fn gossip_block_validator_rejects_epoch_beyond_finality() {
510 let block = make_valid_gossip_block(); let genesis = make_genesis();
512 let seen = SeenBlockCache::default();
513
514 let err = GossipBlockValidator::new(&block)
515 .validate_pre_fetch(&genesis, 30, 100, None, &seen)
516 .unwrap_err();
517 assert!(matches!(
518 err,
519 GossipBlockRejectReason::EpochBeyondFinality { .. }
520 ));
521 }
522
523 #[test]
524 fn gossip_block_validator_rejects_missing_election_proof() {
525 let block = make_gossip_block_with(|h| h.election_proof = None);
526 let genesis = make_genesis();
527 let seen = SeenBlockCache::default();
528
529 let err = GossipBlockValidator::new(&block)
530 .validate_pre_fetch(&genesis, 30, 0, None, &seen)
531 .unwrap_err();
532 assert!(matches!(err, GossipBlockRejectReason::MissingElectionProof));
533 }
534
535 #[test]
536 fn gossip_block_validator_rejects_zero_win_count() {
537 let block = make_gossip_block_with(|h| {
538 h.election_proof = Some(ElectionProof {
539 win_count: 0,
540 vrfproof: Default::default(),
541 })
542 });
543 let genesis = make_genesis();
544 let seen = SeenBlockCache::default();
545
546 let err = GossipBlockValidator::new(&block)
547 .validate_pre_fetch(&genesis, 30, 0, None, &seen)
548 .unwrap_err();
549 assert!(matches!(err, GossipBlockRejectReason::InvalidWinCount(0)));
550 }
551
552 #[test]
553 fn gossip_block_validator_rejects_missing_signature() {
554 let block = make_gossip_block_with(|h| h.signature = None);
555 let genesis = make_genesis();
556 let seen = SeenBlockCache::default();
557
558 let err = GossipBlockValidator::new(&block)
559 .validate_pre_fetch(&genesis, 30, 0, None, &seen)
560 .unwrap_err();
561 assert!(matches!(err, GossipBlockRejectReason::MissingSignature));
562 }
563
564 #[test]
565 fn gossip_block_validator_rejects_too_many_messages() {
566 let mut block = make_valid_gossip_block();
567 block.bls_messages = vec![Cid::default(); 10_001];
568 let genesis = make_genesis();
569 let seen = SeenBlockCache::default();
570
571 let err = GossipBlockValidator::new(&block)
572 .validate_pre_fetch(&genesis, 30, 0, None, &seen)
573 .unwrap_err();
574 assert!(matches!(err, GossipBlockRejectReason::TooManyMessages(_)));
575 }
576
577 #[test]
578 fn gossip_block_validator_rejects_negative_epoch() {
579 let block = make_gossip_block_with(|h| h.epoch = -1);
580 let genesis = make_genesis();
581 let seen = SeenBlockCache::default();
582
583 let err = GossipBlockValidator::new(&block)
584 .validate_pre_fetch(&genesis, 30, 0, None, &seen)
585 .unwrap_err();
586 assert!(matches!(err, GossipBlockRejectReason::NegativeEpoch(-1)));
587 }
588
589 #[test]
590 fn gossip_block_validator_rejects_timestamp_mismatch() {
591 let block = make_gossip_block_with(|h| h.timestamp = 999);
594 let genesis = make_genesis();
595 let seen = SeenBlockCache::default();
596
597 let err = GossipBlockValidator::new(&block)
598 .validate_pre_fetch(&genesis, 30, 0, None, &seen)
599 .unwrap_err();
600 assert!(matches!(
601 err,
602 GossipBlockRejectReason::TimestampMismatch { .. }
603 ));
604 }
605
606 #[test]
610 fn timestamp_check_survives_extreme_epoch_when_clock_is_behind_genesis() {
611 let genesis = Tipset::from(CachingBlockHeader::new(RawBlockHeader {
612 timestamp: u64::MAX,
613 ..Default::default()
614 }));
615
616 for timestamp in [0, u64::MAX] {
619 let block = make_gossip_block_with(|h| {
620 h.epoch = i64::MAX;
621 h.timestamp = timestamp;
622 });
623 let err = GossipBlockValidator::new(&block)
624 .validate_pre_fetch(&genesis, 30, 0, None, &SeenBlockCache::default())
625 .unwrap_err();
626 assert!(
627 matches!(err, GossipBlockRejectReason::EpochTooFarAhead(_)),
628 "timestamp {timestamp}: {err}"
629 );
630 }
631 }
632
633 #[test]
634 fn rejected_block_not_cached_as_seen() {
635 let block = make_gossip_block_with(|h| h.epoch = i64::MAX);
640 let genesis = make_genesis();
641 let seen = SeenBlockCache::default();
642
643 let err = GossipBlockValidator::new(&block)
645 .validate_pre_fetch(&genesis, 30, 0, None, &seen)
646 .unwrap_err();
647 assert!(matches!(err, GossipBlockRejectReason::EpochTooFarAhead(_)));
648
649 let err = GossipBlockValidator::new(&block)
651 .validate_pre_fetch(&genesis, 30, 0, None, &seen)
652 .unwrap_err();
653 assert!(matches!(err, GossipBlockRejectReason::EpochTooFarAhead(_)));
654 }
655
656 #[test]
657 fn seen_block_cache_deduplicates() {
658 let cache = SeenBlockCache::default();
659 let cid = Cid::default();
660
661 assert!(!cache.test_and_insert(&cid));
662 assert!(cache.test_and_insert(&cid));
663 }
664}