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 =
308 genesis_tipset.min_timestamp() + (epoch as u64).saturating_mul(u64::from(block_delay));
309 if timestamp != expected {
310 return Err(GossipBlockRejectReason::TimestampMismatch {
311 timestamp,
312 epoch,
313 expected,
314 });
315 }
316 Ok(())
317 }
318
319 fn validate_election_proof(&self) -> Result<(), GossipBlockRejectReason> {
320 match &self.block.header.election_proof {
321 None => Err(GossipBlockRejectReason::MissingElectionProof),
322 Some(proof) if proof.win_count < 1 => {
323 Err(GossipBlockRejectReason::InvalidWinCount(proof.win_count))
324 }
325 _ => Ok(()),
326 }
327 }
328
329 fn validate_signature_present(&self) -> Result<(), GossipBlockRejectReason> {
330 if self.block.header.signature.is_none() {
331 return Err(GossipBlockRejectReason::MissingSignature);
332 }
333 Ok(())
334 }
335
336 fn validate_message_count(&self) -> Result<(), GossipBlockRejectReason> {
337 let count = self.block.bls_messages.len() + self.block.secpk_messages.len();
338 if count > BLOCK_MESSAGE_LIMIT {
339 return Err(GossipBlockRejectReason::TooManyMessages(count));
340 }
341 Ok(())
342 }
343}
344
345#[cfg(test)]
346mod tests {
347 use std::convert::TryFrom;
348
349 use crate::blocks::{CachingBlockHeader, ElectionProof, GossipBlock, RawBlockHeader, Tipset};
350 use crate::chain_sync::bad_block_cache::{BadBlockCache, SeenBlockCache};
351 use crate::db::MemoryDB;
352 use crate::message::SignedMessage;
353 use crate::shim::crypto::{Signature, SignatureType};
354 use crate::shim::message::Message;
355 use crate::test_utils::construct_messages;
356 use crate::utils::encoding::from_slice_with_fallback;
357 use base64::{Engine, prelude::BASE64_STANDARD};
358 use cid::Cid;
359
360 use super::{GossipBlockRejectReason, GossipBlockValidator, TipsetValidator};
361
362 #[test]
363 fn compute_msg_meta_given_msgs_test() {
364 let blockstore = MemoryDB::default();
365
366 let (bls, secp) = construct_messages();
367
368 let expected_root =
369 Cid::try_from("bafy2bzaceasssikoiintnok7f3sgnekfifarzobyr3r4f25sgxmn23q4c35ic")
370 .unwrap();
371
372 let root = TipsetValidator::compute_msg_root(&blockstore, &[bls], &[secp])
373 .expect("Computing message root should succeed");
374 assert_eq!(root, expected_root);
375 }
376
377 #[test]
378 fn empty_msg_meta_vector() {
379 let blockstore = MemoryDB::default();
380 let usm: Vec<Message> =
381 from_slice_with_fallback(&BASE64_STANDARD.decode("gA==").unwrap()).unwrap();
382 let sm: Vec<SignedMessage> =
383 from_slice_with_fallback(&BASE64_STANDARD.decode("gA==").unwrap()).unwrap();
384
385 assert_eq!(
386 TipsetValidator::compute_msg_root(&blockstore, &usm, &sm)
387 .expect("Computing message root should succeed")
388 .to_string(),
389 "bafy2bzacecmda75ovposbdateg7eyhwij65zklgyijgcjwynlklmqazpwlhba"
390 );
391 }
392
393 #[test]
394 fn max_allowed_epoch_basic() {
395 assert_eq!(super::max_allowed_epoch(1300, 1000, 30), Some(15));
398 }
399
400 #[test]
401 fn max_allowed_epoch_at_genesis() {
402 assert_eq!(super::max_allowed_epoch(1000, 1000, 30), Some(5));
404 }
405
406 #[test]
407 fn max_allowed_epoch_clock_before_genesis() {
408 assert_eq!(super::max_allowed_epoch(500, 1000, 30), None);
410 }
411
412 #[test]
413 fn max_allowed_epoch_zero_block_delay() {
414 assert_eq!(super::max_allowed_epoch(2000, 1000, 0), None);
416 }
417
418 fn make_gossip_block_with(f: impl FnOnce(&mut RawBlockHeader)) -> GossipBlock {
419 let mut raw = RawBlockHeader {
420 election_proof: Some(ElectionProof {
421 win_count: 1,
422 vrfproof: Default::default(),
423 }),
424 signature: Some(Signature {
425 sig_type: SignatureType::Bls,
426 bytes: vec![0u8; 96],
427 }),
428 ..Default::default()
429 };
430 f(&mut raw);
431 GossipBlock {
432 header: CachingBlockHeader::from(raw),
433 bls_messages: vec![],
434 secpk_messages: vec![],
435 }
436 }
437
438 fn make_valid_gossip_block() -> GossipBlock {
439 make_gossip_block_with(|_| {})
440 }
441
442 fn make_genesis() -> Tipset {
443 Tipset::from(CachingBlockHeader::default())
444 }
445
446 #[test]
447 fn gossip_block_validator_accepts_valid_block() {
448 let block = make_valid_gossip_block();
449 let genesis = make_genesis();
450 let seen = SeenBlockCache::default();
451
452 let result = GossipBlockValidator::new(&block).validate_pre_fetch(
453 &genesis, 30, 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, 0, None, &seen)
470 .is_ok()
471 );
472
473 let err = GossipBlockValidator::new(&block)
474 .validate_pre_fetch(&genesis, 30, 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, 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, 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, 100, 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, 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, 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, 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, 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, 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, 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, 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, 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}