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<DB: Blockstore>(
82 &self,
83 chainstore: &ChainStore<DB>,
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.blockstore(), block)?;
102 if let Some(bad_block_cache) = bad_block_cache
103 && bad_block_cache.peek(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 {0} is beyond finality (heaviest: {1})")]
173 EpochBeyondFinality(ChainEpoch, ChainEpoch),
174 #[error("block epoch {0} is negative")]
175 NegativeEpoch(ChainEpoch),
176 #[error("block timestamp {timestamp} inconsistent with epoch {epoch} (expected {expected})")]
177 TimestampMismatch {
178 timestamp: u64,
179 epoch: ChainEpoch,
180 expected: u64,
181 },
182 #[error("block has no signature")]
183 MissingSignature,
184 #[error("block has no election proof")]
185 MissingElectionProof,
186 #[error("block election proof has win_count {0} < 1")]
187 InvalidWinCount(i64),
188 #[error("block has {0} messages, exceeding limit of {BLOCK_MESSAGE_LIMIT}")]
189 TooManyMessages(usize),
190 #[error("block CID {0} is in bad block cache")]
191 BadBlock(Cid),
192 #[error("duplicate block CID {0}")]
193 DuplicateBlock(Cid),
194}
195
196impl GossipBlockRejectReason {
197 pub fn label(&self) -> &'static str {
198 match self {
199 Self::EpochTooFarAhead(_) => "epoch_too_far_ahead",
200 Self::EpochBeyondFinality(_, _) => "epoch_beyond_finality",
201 Self::NegativeEpoch(_) => "negative_epoch",
202 Self::TimestampMismatch { .. } => "timestamp_mismatch",
203 Self::MissingSignature => "missing_signature",
204 Self::MissingElectionProof => "missing_election_proof",
205 Self::InvalidWinCount(_) => "invalid_win_count",
206 Self::TooManyMessages(_) => "too_many_messages",
207 Self::BadBlock(_) => "bad_block",
208 Self::DuplicateBlock(_) => "duplicate_block",
209 }
210 }
211}
212
213pub struct GossipBlockValidator<'a> {
217 block: &'a GossipBlock,
218}
219
220impl<'a> GossipBlockValidator<'a> {
221 pub fn new(block: &'a GossipBlock) -> Self {
222 Self { block }
223 }
224
225 pub fn validate_pre_fetch(
228 &self,
229 genesis_tipset: &Tipset,
230 block_delay: u32,
231 chain_finality: ChainEpoch,
232 heaviest_epoch: ChainEpoch,
233 bad_block_cache: Option<&BadBlockCache>,
234 seen_block_cache: &SeenBlockCache,
235 ) -> Result<(), GossipBlockRejectReason> {
236 let cid = *self.block.header.cid();
237 Self::check_bad_block_cache(cid, bad_block_cache)?;
238 self.validate_epoch_range(genesis_tipset, block_delay, chain_finality, heaviest_epoch)?;
239 self.validate_timestamp(genesis_tipset, block_delay)?;
240 self.validate_election_proof()?;
241 self.validate_signature_present()?;
242 self.validate_message_count()?;
243 Self::check_duplicate(cid, seen_block_cache)?;
246 Ok(())
247 }
248
249 fn check_duplicate(
250 cid: Cid,
251 seen_block_cache: &SeenBlockCache,
252 ) -> Result<(), GossipBlockRejectReason> {
253 if seen_block_cache.test_and_insert(&cid) {
254 return Err(GossipBlockRejectReason::DuplicateBlock(cid));
255 }
256 Ok(())
257 }
258
259 fn check_bad_block_cache(
260 cid: Cid,
261 bad_block_cache: Option<&BadBlockCache>,
262 ) -> Result<(), GossipBlockRejectReason> {
263 if let Some(cache) = bad_block_cache
264 && cache.peek(&cid).is_some()
265 {
266 return Err(GossipBlockRejectReason::BadBlock(cid));
267 }
268 Ok(())
269 }
270
271 fn validate_epoch_range(
272 &self,
273 genesis_tipset: &Tipset,
274 block_delay: u32,
275 chain_finality: ChainEpoch,
276 heaviest_epoch: ChainEpoch,
277 ) -> Result<(), GossipBlockRejectReason> {
278 let epoch = self.block.header.epoch;
279 if epoch < 0 {
280 return Err(GossipBlockRejectReason::NegativeEpoch(epoch));
281 }
282 let max = max_allowed_epoch(now_secs(), genesis_tipset.min_timestamp(), block_delay)
283 .unwrap_or(ChainEpoch::MAX);
284 if epoch > max {
285 return Err(GossipBlockRejectReason::EpochTooFarAhead(epoch));
286 }
287 if heaviest_epoch.saturating_sub(epoch) > chain_finality {
288 return Err(GossipBlockRejectReason::EpochBeyondFinality(
289 epoch,
290 heaviest_epoch,
291 ));
292 }
293 Ok(())
294 }
295
296 fn validate_timestamp(
299 &self,
300 genesis_tipset: &Tipset,
301 block_delay: u32,
302 ) -> Result<(), GossipBlockRejectReason> {
303 let epoch = self.block.header.epoch;
304 let timestamp = self.block.header.timestamp;
305 let expected =
307 genesis_tipset.min_timestamp() + (epoch as u64).saturating_mul(u64::from(block_delay));
308 if timestamp != expected {
309 return Err(GossipBlockRejectReason::TimestampMismatch {
310 timestamp,
311 epoch,
312 expected,
313 });
314 }
315 Ok(())
316 }
317
318 fn validate_election_proof(&self) -> Result<(), GossipBlockRejectReason> {
319 match &self.block.header.election_proof {
320 None => Err(GossipBlockRejectReason::MissingElectionProof),
321 Some(proof) if proof.win_count < 1 => {
322 Err(GossipBlockRejectReason::InvalidWinCount(proof.win_count))
323 }
324 _ => Ok(()),
325 }
326 }
327
328 fn validate_signature_present(&self) -> Result<(), GossipBlockRejectReason> {
329 if self.block.header.signature.is_none() {
330 return Err(GossipBlockRejectReason::MissingSignature);
331 }
332 Ok(())
333 }
334
335 fn validate_message_count(&self) -> Result<(), GossipBlockRejectReason> {
336 let count = self.block.bls_messages.len() + self.block.secpk_messages.len();
337 if count > BLOCK_MESSAGE_LIMIT {
338 return Err(GossipBlockRejectReason::TooManyMessages(count));
339 }
340 Ok(())
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 use std::convert::TryFrom;
347
348 use crate::blocks::{CachingBlockHeader, ElectionProof, GossipBlock, RawBlockHeader, Tipset};
349 use crate::chain_sync::bad_block_cache::{BadBlockCache, SeenBlockCache};
350 use crate::db::MemoryDB;
351 use crate::message::SignedMessage;
352 use crate::shim::crypto::{Signature, SignatureType};
353 use crate::shim::message::Message;
354 use crate::test_utils::construct_messages;
355 use crate::utils::encoding::from_slice_with_fallback;
356 use base64::{Engine, prelude::BASE64_STANDARD};
357 use cid::Cid;
358
359 use super::{GossipBlockRejectReason, GossipBlockValidator, TipsetValidator};
360
361 #[test]
362 fn compute_msg_meta_given_msgs_test() {
363 let blockstore = MemoryDB::default();
364
365 let (bls, secp) = construct_messages();
366
367 let expected_root =
368 Cid::try_from("bafy2bzaceasssikoiintnok7f3sgnekfifarzobyr3r4f25sgxmn23q4c35ic")
369 .unwrap();
370
371 let root = TipsetValidator::compute_msg_root(&blockstore, &[bls], &[secp])
372 .expect("Computing message root should succeed");
373 assert_eq!(root, expected_root);
374 }
375
376 #[test]
377 fn empty_msg_meta_vector() {
378 let blockstore = MemoryDB::default();
379 let usm: Vec<Message> =
380 from_slice_with_fallback(&BASE64_STANDARD.decode("gA==").unwrap()).unwrap();
381 let sm: Vec<SignedMessage> =
382 from_slice_with_fallback(&BASE64_STANDARD.decode("gA==").unwrap()).unwrap();
383
384 assert_eq!(
385 TipsetValidator::compute_msg_root(&blockstore, &usm, &sm)
386 .expect("Computing message root should succeed")
387 .to_string(),
388 "bafy2bzacecmda75ovposbdateg7eyhwij65zklgyijgcjwynlklmqazpwlhba"
389 );
390 }
391
392 #[test]
393 fn max_allowed_epoch_basic() {
394 assert_eq!(super::max_allowed_epoch(1300, 1000, 30), Some(15));
397 }
398
399 #[test]
400 fn max_allowed_epoch_at_genesis() {
401 assert_eq!(super::max_allowed_epoch(1000, 1000, 30), Some(5));
403 }
404
405 #[test]
406 fn max_allowed_epoch_clock_before_genesis() {
407 assert_eq!(super::max_allowed_epoch(500, 1000, 30), None);
409 }
410
411 #[test]
412 fn max_allowed_epoch_zero_block_delay() {
413 assert_eq!(super::max_allowed_epoch(2000, 1000, 0), None);
415 }
416
417 fn make_gossip_block_with(f: impl FnOnce(&mut RawBlockHeader)) -> GossipBlock {
418 let mut raw = RawBlockHeader {
419 election_proof: Some(ElectionProof {
420 win_count: 1,
421 vrfproof: Default::default(),
422 }),
423 signature: Some(Signature {
424 sig_type: SignatureType::Bls,
425 bytes: vec![0u8; 96],
426 }),
427 ..Default::default()
428 };
429 f(&mut raw);
430 GossipBlock {
431 header: CachingBlockHeader::from(raw),
432 bls_messages: vec![],
433 secpk_messages: vec![],
434 }
435 }
436
437 fn make_valid_gossip_block() -> GossipBlock {
438 make_gossip_block_with(|_| {})
439 }
440
441 fn make_genesis() -> Tipset {
442 Tipset::from(CachingBlockHeader::default())
443 }
444
445 #[test]
446 fn gossip_block_validator_accepts_valid_block() {
447 let block = make_valid_gossip_block();
448 let genesis = make_genesis();
449 let seen = SeenBlockCache::default();
450
451 let result = GossipBlockValidator::new(&block).validate_pre_fetch(
452 &genesis, 30, 900, 0, None, &seen,
457 );
458 assert!(result.is_ok());
459 }
460
461 #[test]
462 fn gossip_block_validator_rejects_duplicate() {
463 let block = make_valid_gossip_block();
464 let genesis = make_genesis();
465 let seen = SeenBlockCache::default();
466
467 assert!(
468 GossipBlockValidator::new(&block)
469 .validate_pre_fetch(&genesis, 30, 900, 0, None, &seen)
470 .is_ok()
471 );
472
473 let err = GossipBlockValidator::new(&block)
474 .validate_pre_fetch(&genesis, 30, 900, 0, None, &seen)
475 .unwrap_err();
476 assert!(matches!(err, GossipBlockRejectReason::DuplicateBlock(_)));
477 }
478
479 #[test]
480 fn gossip_block_validator_rejects_bad_block() {
481 let block = make_valid_gossip_block();
482 let genesis = make_genesis();
483 let seen = SeenBlockCache::default();
484 let bad_cache = BadBlockCache::default();
485 bad_cache.push(*block.header.cid());
486
487 let err = GossipBlockValidator::new(&block)
488 .validate_pre_fetch(&genesis, 30, 900, 0, Some(&bad_cache), &seen)
489 .unwrap_err();
490 assert!(matches!(err, GossipBlockRejectReason::BadBlock(_)));
491 }
492
493 #[test]
494 fn gossip_block_validator_rejects_epoch_too_far_ahead() {
495 let block = make_gossip_block_with(|h| h.epoch = i64::MAX);
496 let genesis = make_genesis();
497 let seen = SeenBlockCache::default();
498
499 let err = GossipBlockValidator::new(&block)
500 .validate_pre_fetch(&genesis, 30, 900, 0, None, &seen)
501 .unwrap_err();
502 assert!(matches!(err, GossipBlockRejectReason::EpochTooFarAhead(_)));
503 }
504
505 #[test]
506 fn gossip_block_validator_rejects_epoch_beyond_finality() {
507 let block = make_valid_gossip_block(); let genesis = make_genesis();
509 let seen = SeenBlockCache::default();
510
511 let err = GossipBlockValidator::new(&block)
512 .validate_pre_fetch(&genesis, 30, 900, 1000, None, &seen)
513 .unwrap_err();
514 assert!(matches!(
515 err,
516 GossipBlockRejectReason::EpochBeyondFinality(_, _)
517 ));
518 }
519
520 #[test]
521 fn gossip_block_validator_rejects_missing_election_proof() {
522 let block = make_gossip_block_with(|h| h.election_proof = None);
523 let genesis = make_genesis();
524 let seen = SeenBlockCache::default();
525
526 let err = GossipBlockValidator::new(&block)
527 .validate_pre_fetch(&genesis, 30, 900, 0, None, &seen)
528 .unwrap_err();
529 assert!(matches!(err, GossipBlockRejectReason::MissingElectionProof));
530 }
531
532 #[test]
533 fn gossip_block_validator_rejects_zero_win_count() {
534 let block = make_gossip_block_with(|h| {
535 h.election_proof = Some(ElectionProof {
536 win_count: 0,
537 vrfproof: Default::default(),
538 })
539 });
540 let genesis = make_genesis();
541 let seen = SeenBlockCache::default();
542
543 let err = GossipBlockValidator::new(&block)
544 .validate_pre_fetch(&genesis, 30, 900, 0, None, &seen)
545 .unwrap_err();
546 assert!(matches!(err, GossipBlockRejectReason::InvalidWinCount(0)));
547 }
548
549 #[test]
550 fn gossip_block_validator_rejects_missing_signature() {
551 let block = make_gossip_block_with(|h| h.signature = None);
552 let genesis = make_genesis();
553 let seen = SeenBlockCache::default();
554
555 let err = GossipBlockValidator::new(&block)
556 .validate_pre_fetch(&genesis, 30, 900, 0, None, &seen)
557 .unwrap_err();
558 assert!(matches!(err, GossipBlockRejectReason::MissingSignature));
559 }
560
561 #[test]
562 fn gossip_block_validator_rejects_too_many_messages() {
563 let mut block = make_valid_gossip_block();
564 block.bls_messages = vec![Cid::default(); 10_001];
565 let genesis = make_genesis();
566 let seen = SeenBlockCache::default();
567
568 let err = GossipBlockValidator::new(&block)
569 .validate_pre_fetch(&genesis, 30, 900, 0, None, &seen)
570 .unwrap_err();
571 assert!(matches!(err, GossipBlockRejectReason::TooManyMessages(_)));
572 }
573
574 #[test]
575 fn gossip_block_validator_rejects_negative_epoch() {
576 let block = make_gossip_block_with(|h| h.epoch = -1);
577 let genesis = make_genesis();
578 let seen = SeenBlockCache::default();
579
580 let err = GossipBlockValidator::new(&block)
581 .validate_pre_fetch(&genesis, 30, 900, 0, None, &seen)
582 .unwrap_err();
583 assert!(matches!(err, GossipBlockRejectReason::NegativeEpoch(-1)));
584 }
585
586 #[test]
587 fn gossip_block_validator_rejects_timestamp_mismatch() {
588 let block = make_gossip_block_with(|h| h.timestamp = 999);
591 let genesis = make_genesis();
592 let seen = SeenBlockCache::default();
593
594 let err = GossipBlockValidator::new(&block)
595 .validate_pre_fetch(&genesis, 30, 900, 0, None, &seen)
596 .unwrap_err();
597 assert!(matches!(
598 err,
599 GossipBlockRejectReason::TimestampMismatch { .. }
600 ));
601 }
602
603 #[test]
604 fn rejected_block_not_cached_as_seen() {
605 let block = make_gossip_block_with(|h| h.epoch = i64::MAX);
610 let genesis = make_genesis();
611 let seen = SeenBlockCache::default();
612
613 let err = GossipBlockValidator::new(&block)
615 .validate_pre_fetch(&genesis, 30, 900, 0, None, &seen)
616 .unwrap_err();
617 assert!(matches!(err, GossipBlockRejectReason::EpochTooFarAhead(_)));
618
619 let err = GossipBlockValidator::new(&block)
621 .validate_pre_fetch(&genesis, 30, 900, 0, None, &seen)
622 .unwrap_err();
623 assert!(matches!(err, GossipBlockRejectReason::EpochTooFarAhead(_)));
624 }
625
626 #[test]
627 fn seen_block_cache_deduplicates() {
628 let cache = SeenBlockCache::default();
629 let cid = Cid::default();
630
631 assert!(!cache.test_and_insert(&cid));
632 assert!(cache.test_and_insert(&cid));
633 }
634}