1use crate::chain_sync::BadBlockCache;
5use crate::db::DbImpl;
6use crate::networks::Height;
7use crate::prelude::*;
8use crate::shim::clock::ALLOWABLE_CLOCK_DRIFT;
9use crate::shim::crypto::SignatureType;
10use crate::shim::message::Message;
11use crate::shim::version::NetworkVersion;
12use crate::shim::{
13 address::Address, crypto::verify_bls_aggregate, econ::BLOCK_GAS_LIMIT,
14 gas::price_list_by_network_version, state_tree::StateTree,
15};
16use crate::state_manager::ExecutedTipset;
17use crate::state_manager::{Error as StateManagerError, StateManager, utils::is_valid_for_sending};
18use crate::utils::encoding::calc_encoded_len;
19use crate::{
20 blocks::{Block, CachingBlockHeader, Error as ForestBlockError, FullTipset, Tipset},
21 fil_cns::{self, FilecoinConsensus, FilecoinConsensusError},
22};
23use crate::{
24 chain::{ChainStore, Error as ChainStoreError},
25 metrics::HistogramTimerExt,
26};
27use crate::{
28 eth::is_valid_eth_tx_for_sending,
29 message::{MessageRead as _, valid_for_block_inclusion},
30};
31use ahash::HashMap;
32use futures::TryFutureExt;
33use nunny::Vec as NonEmpty;
34use thiserror::Error;
35use tokio::task::JoinSet;
36use tracing::{trace, warn};
37
38use crate::chain_sync::{consensus::collect_errs, metrics, validation::TipsetValidator};
39
40#[derive(Debug, Error)]
41pub enum TipsetSyncerError {
42 #[error("Block must have a signature")]
43 BlockWithoutSignature,
44 #[error("Block without BLS aggregate signature")]
45 BlockWithoutBlsAggregate,
46 #[error("Block received from the future: now = {0}, block = {1}")]
47 TimeTravellingBlock(u64, u64),
48 #[error("Validation error: {0}")]
49 Validation(String),
50 #[error("Parent chain state mismatch: {0}")]
54 ParentChainStateMismatch(String),
55 #[error("Processing error: {0}")]
56 Calculation(String),
57 #[error("Chain store error: {0}")]
58 ChainStore(#[from] ChainStoreError),
59 #[error("StateManager error: {0}")]
60 StateManager(#[from] StateManagerError),
61 #[error("Block error: {0}")]
62 BlockError(#[from] ForestBlockError),
63 #[error("Querying tipsets from the network failed: {0}")]
64 NetworkTipsetQueryFailed(String),
65 #[error("BLS aggregate signature {0} was invalid for msgs {1}")]
66 BlsAggregateSignatureInvalid(String, String),
67 #[error("Message signature invalid: {0}")]
68 MessageSignatureInvalid(String),
69 #[error("Block message root does not match: expected {0}, computed {1}")]
70 BlockMessageRootInvalid(String, String),
71 #[error("Computing message root failed: {0}")]
72 ComputingMessageRoot(String),
73 #[error("Resolving address from message failed: {0}")]
74 ResolvingAddressFromMessage(String),
75 #[error("Loading tipset parent from the store failed: {0}")]
76 TipsetParentNotFound(ChainStoreError),
77 #[error("Consensus error: {0}")]
78 ConsensusError(FilecoinConsensusError),
79 #[error(
80 "Block had a signed message at index {0} whose signature type {1} is not allowed in the SECP message list"
81 )]
82 SecpSignatureTypeInvalid(usize, SignatureType),
83}
84
85impl From<tokio::task::JoinError> for TipsetSyncerError {
86 fn from(err: tokio::task::JoinError) -> Self {
87 TipsetSyncerError::NetworkTipsetQueryFailed(format!("{err}"))
88 }
89}
90
91impl TipsetSyncerError {
92 fn concat(errs: NonEmpty<TipsetSyncerError>) -> Self {
95 let msg = errs.iter().map(|e| e.to_string()).collect_vec().join(", ");
96
97 if errs
98 .iter()
99 .any(|e| matches!(e, TipsetSyncerError::ParentChainStateMismatch(_)))
100 {
101 TipsetSyncerError::ParentChainStateMismatch(msg)
102 } else {
103 TipsetSyncerError::Validation(msg)
104 }
105 }
106}
107
108pub async fn validate_tipset(
113 state_manager: &StateManager,
114 full_tipset: FullTipset,
115 bad_block_cache: Option<BadBlockCache>,
116) -> Result<(), TipsetSyncerError> {
117 if full_tipset
118 .key()
119 .eq(state_manager.chain_store().genesis_tipset().key())
120 {
121 trace!("Skipping genesis tipset validation");
122 return Ok(());
123 }
124
125 let timer = metrics::TIPSET_PROCESSING_TIME.start_timer();
126
127 let epoch = full_tipset.epoch();
128 let parent_state = *full_tipset.parent_state();
129 let tipset_key = full_tipset.key();
130 trace!("Tipset keys: {tipset_key}");
131 let blocks = full_tipset.into_blocks();
132 let mut validations = JoinSet::new();
133 for b in blocks {
134 validations.spawn(validate_block(state_manager.shallow_clone(), Arc::new(b)));
135 }
136
137 while let Some(result) = validations.join_next().await {
138 match result? {
139 Ok(block) => {
140 state_manager
141 .chain_store()
142 .add_to_tipset_tracker(block.header());
143 }
144 Err(boxed) => {
145 let (cid, why) = *boxed;
146 warn!(
147 "Validating block [CID = {cid}, PARENT_STATE = {parent_state}] in EPOCH = {epoch} failed: {why}",
148 );
149 match &why {
150 TipsetSyncerError::TimeTravellingBlock(_, _) => {
151 }
154 _ => {
155 if StateTree::new_from_root(state_manager.db(), &parent_state).is_ok()
157 && let Some(bad_block_cache) = bad_block_cache
158 {
159 bad_block_cache.push(cid);
160 }
161 }
162 };
163 return Err(why);
164 }
165 }
166 }
167 drop(timer);
168 Ok(())
169}
170
171async fn validate_block(
191 state_manager: StateManager,
192 block: Arc<Block>,
193) -> Result<Arc<Block>, Box<(Cid, TipsetSyncerError)>> {
194 let consensus = FilecoinConsensus::new(state_manager.beacon_schedule().clone());
195 trace!(
196 "Validating block: epoch = {}, weight = {}, key = {}",
197 block.header().epoch,
198 block.header().weight,
199 block.header().cid(),
200 );
201 let chain_store = state_manager.chain_store().shallow_clone();
202 let block_cid = block.cid();
203
204 let is_validated = chain_store.is_block_validated(block_cid);
206 if is_validated {
207 return Ok(block);
208 }
209
210 let _timer = metrics::BLOCK_VALIDATION_TIME.start_timer();
211
212 let header = block.header();
213
214 block_sanity_checks(header).map_err(|e| Box::new((*block_cid, e)))?;
216 block_timestamp_checks(header).map_err(|e| Box::new((*block_cid, e)))?;
217
218 let base_tipset = chain_store
219 .chain_index()
220 .load_required_tipset(&header.parents)
221 .map_err(|why| Box::new((*block_cid, TipsetSyncerError::TipsetParentNotFound(why))))?;
227
228 let lookback_state = ChainStore::get_lookback_tipset_for_round(
230 state_manager.chain_store().chain_index().shallow_clone(),
231 state_manager.chain_config().shallow_clone(),
232 base_tipset.shallow_clone(),
233 block.header().epoch,
234 )
235 .await
236 .map_err(|e| Box::new((*block_cid, e.into())))
237 .map(|(_, s)| Arc::new(s))?;
238
239 let work_addr = state_manager
242 .get_miner_work_addr(*lookback_state, &header.miner_address)
243 .map_err(|e| Box::new((*block_cid, e.into())))?;
244
245 let mut validations = JoinSet::new();
247
248 validations.spawn(check_block_messages(
250 state_manager.shallow_clone(),
251 block.shallow_clone(),
252 base_tipset.shallow_clone(),
253 ));
254
255 validations.spawn_blocking({
257 let smoke_height = state_manager.chain_config().epoch(Height::Smoke);
258 let firehorse_height = state_manager.chain_config().epoch(Height::FireHorse);
259 let base_tipset = base_tipset.shallow_clone();
260 let block_store = state_manager.db_owned();
261 let block = block.shallow_clone();
262 move || {
263 let base_fee = crate::chain::compute_base_fee(
264 &block_store,
265 &base_tipset,
266 smoke_height,
267 firehorse_height,
268 )
269 .map_err(|e| {
270 TipsetSyncerError::Validation(format!("Could not compute base fee: {e}"))
271 })?;
272 let parent_base_fee = &block.header.parent_base_fee;
273 if &base_fee != parent_base_fee {
274 return Err(TipsetSyncerError::Validation(format!(
275 "base fee doesn't match: {parent_base_fee} (header), {base_fee} (computed)"
276 )));
277 }
278 Ok(())
279 }
280 });
281
282 validations.spawn_blocking({
284 let block_store = state_manager.db_owned();
285 let base_tipset = base_tipset.shallow_clone();
286 let weight = header.weight.clone();
287 move || {
288 let calc_weight = fil_cns::weight(&block_store, &base_tipset).map_err(|e| {
289 TipsetSyncerError::Calculation(format!("Error calculating weight: {e:#}"))
290 })?;
291 if weight != calc_weight {
292 return Err(TipsetSyncerError::Validation(format!(
293 "Parent weight doesn't match: {weight} (header), {calc_weight} (computed)"
294 )));
295 }
296 Ok(())
297 }
298 });
299
300 validations.spawn({
302 let state_manager = state_manager.shallow_clone();
303 let block = block.shallow_clone();
304 async move {
305 let header = block.header();
306 let ExecutedTipset {
307 state_root,
308 receipt_root,
309 ..
310 } = state_manager
311 .load_executed_tipset(&base_tipset)
312 .await
313 .map_err(|e| {
314 TipsetSyncerError::Calculation(format!("Failed to calculate state: {e:#}"))
315 })?;
316
317 if state_root != header.state_root {
318 return Err(TipsetSyncerError::ParentChainStateMismatch(format!(
319 "Parent state root did not match computed state: {} (header), {} (computed)",
320 header.state_root, state_root,
321 )));
322 }
323
324 if receipt_root != header.message_receipts {
325 return Err(TipsetSyncerError::ParentChainStateMismatch(format!(
326 "Parent receipt root did not match computed root: {} (header), {} (computed)",
327 header.message_receipts, receipt_root
328 )));
329 }
330 Ok(())
331 }
332 });
333
334 validations.spawn_blocking({
336 let block = block.shallow_clone();
337 move || {
338 block.header().verify_signature_against(&work_addr)?;
339 Ok(())
340 }
341 });
342
343 validations.spawn({
344 let block = block.shallow_clone();
345 async move {
346 consensus
347 .validate_block(state_manager, block)
348 .map_err(|errs| {
349 TipsetSyncerError::concat(
356 errs.into_iter_ne()
357 .map(TipsetSyncerError::ConsensusError)
358 .collect_vec(),
359 )
360 })
361 .await
362 }
363 });
364
365 if let Err(errs) = collect_errs(validations).await {
367 return Err(Box::new((*block_cid, TipsetSyncerError::concat(errs))));
368 }
369
370 chain_store.mark_block_as_validated(block_cid);
371
372 Ok(block)
373}
374
375async fn check_block_messages(
386 state_manager: StateManager,
387 block: Arc<Block>,
388 base_tipset: Tipset,
389) -> Result<(), TipsetSyncerError> {
390 let network_version = state_manager
391 .chain_config()
392 .network_version(block.header.epoch);
393 let eth_chain_id = state_manager.chain_config().eth_chain_id;
394
395 if let Some(sig) = &block.header().bls_aggregate {
396 let mut pub_keys = Vec::with_capacity(block.bls_msgs().len());
399 let mut cids = Vec::with_capacity(block.bls_msgs().len());
400 let db = state_manager.db();
401 for m in block.bls_msgs() {
402 let pk = StateManager::get_bls_public_key(db, m.from, *base_tipset.parent_state())?;
403 pub_keys.push(pk);
404 cids.push(m.cid().to_bytes());
405 }
406
407 if !verify_bls_aggregate(
408 &cids.iter().map(|x| x.as_slice()).collect_vec(),
409 &pub_keys,
410 sig,
411 ) {
412 return Err(TipsetSyncerError::BlsAggregateSignatureInvalid(
413 format!("{sig:?}"),
414 format!("{cids:?}"),
415 ));
416 }
417 } else {
418 return Err(TipsetSyncerError::BlockWithoutBlsAggregate);
419 }
420
421 let price_list = price_list_by_network_version(network_version);
422 let mut sum_gas_limit = 0;
423
424 let mut check_msg = |msg: &Message,
426 account_sequences: &mut HashMap<Address, u64>,
427 tree: &StateTree<DbImpl>|
428 -> anyhow::Result<()> {
429 let min_gas = price_list.on_chain_message(calc_encoded_len(msg)?);
431 valid_for_block_inclusion(msg, min_gas.total(), network_version)
432 .map_err(|e| anyhow::anyhow!("{e}"))?;
433 sum_gas_limit += msg.gas_limit;
434 if sum_gas_limit > BLOCK_GAS_LIMIT {
435 anyhow::bail!("block gas limit exceeded");
436 }
437
438 let sequence: u64 = match account_sequences.get(&msg.from()) {
441 Some(sequence) => *sequence,
442 None => {
443 let actor = tree.get_actor(&msg.from)?.ok_or_else(|| {
444 anyhow::anyhow!(
445 "Failed to retrieve nonce for addr: Actor does not exist in state"
446 )
447 })?;
448 let network_version = state_manager
449 .chain_config()
450 .network_version(block.header.epoch);
451 if !is_valid_for_sending(network_version, &actor) {
452 anyhow::bail!("not valid for sending!");
453 }
454 actor.sequence
455 }
456 };
457
458 if sequence != msg.sequence {
460 anyhow::bail!(
461 "Message has incorrect sequence (exp: {} got: {})",
462 sequence,
463 msg.sequence
464 );
465 }
466 account_sequences.insert(msg.from(), sequence + 1);
467 Ok(())
468 };
469
470 let mut account_sequences: HashMap<Address, u64> = HashMap::default();
471 let ExecutedTipset { state_root, .. } = state_manager
472 .load_executed_tipset(&base_tipset)
473 .await
474 .map_err(|e| TipsetSyncerError::Calculation(format!("Could not update state: {e:#}")))?;
475 let tree = StateTree::new_from_root(state_manager.db(), &state_root).map_err(|e| {
476 TipsetSyncerError::Calculation(format!(
477 "Could not load from new state root in state manager: {e:#}"
478 ))
479 })?;
480
481 for (i, msg) in block.bls_msgs().iter().enumerate() {
483 check_msg(msg, &mut account_sequences, &tree).map_err(|e| {
484 TipsetSyncerError::Validation(format!(
485 "Block had invalid BLS message at index {i}: {e:#}"
486 ))
487 })?;
488 }
489
490 for (i, msg) in block.secp_msgs().iter().enumerate() {
492 if network_version >= NetworkVersion::V14
493 && !msg.signature().is_valid_secpk_sig_type(network_version)
494 {
495 return Err(TipsetSyncerError::SecpSignatureTypeInvalid(
496 i,
497 msg.signature().signature_type(),
498 ));
499 }
500 if msg.signature().signature_type() == SignatureType::Delegated
501 && !is_valid_eth_tx_for_sending(eth_chain_id, network_version, msg)
502 {
503 return Err(TipsetSyncerError::Validation(
504 "Network version must be at least NV23 for legacy Ethereum transactions".to_owned(),
505 ));
506 }
507 check_msg(msg.message(), &mut account_sequences, &tree).map_err(|e| {
508 TipsetSyncerError::Validation(format!(
509 "block had an invalid secp message at index {i}: {e:#}"
510 ))
511 })?;
512 let key_addr = state_manager
514 .resolve_to_deterministic_address(msg.from(), &base_tipset)
515 .await
516 .map_err(|e| TipsetSyncerError::ResolvingAddressFromMessage(e.to_string()))?;
517 msg.signature
519 .authenticate_msg(eth_chain_id, msg, &key_addr)
520 .map_err(|e| TipsetSyncerError::MessageSignatureInvalid(e.to_string()))?;
521 }
522
523 let msg_root =
525 TipsetValidator::compute_msg_root(state_manager.db(), block.bls_msgs(), block.secp_msgs())
526 .map_err(|err| TipsetSyncerError::ComputingMessageRoot(err.to_string()))?;
527 if block.header().messages != msg_root {
528 return Err(TipsetSyncerError::BlockMessageRootInvalid(
529 format!("{:?}", block.header().messages),
530 format!("{msg_root:?}"),
531 ));
532 }
533
534 Ok(())
535}
536
537fn block_sanity_checks(header: &CachingBlockHeader) -> Result<(), TipsetSyncerError> {
541 if header.signature.is_none() {
542 return Err(TipsetSyncerError::BlockWithoutSignature);
543 }
544 if header.bls_aggregate.is_none() {
545 return Err(TipsetSyncerError::BlockWithoutBlsAggregate);
546 }
547 Ok(())
548}
549
550fn block_timestamp_checks(header: &CachingBlockHeader) -> Result<(), TipsetSyncerError> {
552 let time_now = chrono::Utc::now().timestamp() as u64;
553 if header.timestamp > time_now.saturating_add(ALLOWABLE_CLOCK_DRIFT) {
554 return Err(TipsetSyncerError::TimeTravellingBlock(
555 time_now,
556 header.timestamp,
557 ));
558 } else if header.timestamp > time_now {
559 warn!(
560 "Got block from the future, but within clock drift threshold, {} > {}",
561 header.timestamp, time_now
562 );
563 }
564 Ok(())
565}
566
567#[cfg(test)]
568mod tests {
569 use super::*;
570
571 #[test]
572 fn concat_preserves_parent_chain_state_mismatch() {
573 let concatenated = TipsetSyncerError::concat(nunny::vec![
574 TipsetSyncerError::Validation("a".into()),
575 TipsetSyncerError::ParentChainStateMismatch("b".into()),
576 ]);
577 assert!(matches!(
578 concatenated,
579 TipsetSyncerError::ParentChainStateMismatch(_)
580 ));
581
582 let concatenated =
583 TipsetSyncerError::concat(nunny::vec![TipsetSyncerError::Validation("a".into())]);
584 assert!(matches!(concatenated, TipsetSyncerError::Validation(_)));
585 }
586
587 mod block_messages {
588 use super::*;
589 use crate::blocks::RawBlockHeader;
590 use crate::db::MemoryDB;
591 use crate::message::SignedMessage;
592 use crate::networks::ChainConfig;
593 use crate::shim::crypto::{BLS_SIG_LEN, SECP_SIG_LEN, Signature};
594 use crate::shim::state_tree::StateTreeVersion;
595
596 fn chain_config() -> Arc<ChainConfig> {
597 Arc::new(ChainConfig::default())
598 }
599
600 fn last_epoch_before_nv14(chain_config: &ChainConfig) -> ChainEpoch {
603 chain_config.epoch(Height::Chocolate)
604 }
605
606 fn first_epoch_at_nv14(chain_config: &ChainConfig) -> ChainEpoch {
607 last_epoch_before_nv14(chain_config) + 1
608 }
609
610 async fn validate(
611 chain_config: Arc<ChainConfig>,
612 epoch: ChainEpoch,
613 signature: Signature,
614 ) -> TipsetSyncerError {
615 let db = Arc::new(MemoryDB::default());
616 let genesis = CachingBlockHeader::new(RawBlockHeader {
617 timestamp: 7777,
618 ..Default::default()
619 });
620 let chain_store = ChainStore::new(db, chain_config, genesis).unwrap();
621 let state_manager = StateManager::new(chain_store).unwrap();
622
623 let state_root = StateTree::new(state_manager.db(), StateTreeVersion::V5)
624 .unwrap()
625 .flush()
626 .unwrap();
627 let base_tipset = state_manager.chain_store().heaviest_tipset();
628 state_manager.insert_executed_tipset(
629 base_tipset.key().clone(),
630 ExecutedTipset {
631 state_root,
632 receipt_root: Cid::default(),
633 executed_messages: Arc::new(vec![]),
634 },
635 );
636
637 let block = Arc::new(Block {
638 header: CachingBlockHeader::new(RawBlockHeader {
639 epoch,
640 bls_aggregate: Some(Signature::new_bls(vec![0; BLS_SIG_LEN])),
641 ..Default::default()
642 }),
643 bls_messages: vec![],
644 secp_messages: vec![SignedMessage::new_unchecked(
645 Message {
646 to: Address::new_id(1),
647 from: Address::new_id(2),
648 gas_limit: u64::from(u32::MAX),
649 ..Default::default()
650 },
651 signature,
652 )],
653 });
654
655 check_block_messages(state_manager, block, base_tipset)
656 .await
657 .unwrap_err()
658 }
659
660 #[tokio::test]
661 async fn bls_signature_type_is_rejected_in_the_secp_message_list() {
662 let chain_config = chain_config();
663 let epoch = first_epoch_at_nv14(&chain_config);
664 let err = validate(
665 chain_config,
666 epoch,
667 Signature::new_bls(vec![0; BLS_SIG_LEN]),
668 )
669 .await;
670 assert!(
671 matches!(
672 err,
673 TipsetSyncerError::SecpSignatureTypeInvalid(0, SignatureType::Bls)
674 ),
675 "got: {err}"
676 );
677 }
678
679 #[tokio::test]
680 async fn secp256k1_signature_type_passes_the_guard() {
681 let chain_config = chain_config();
682 let epoch = first_epoch_at_nv14(&chain_config);
683 let err = validate(
684 chain_config,
685 epoch,
686 Signature::new_secp256k1(vec![0; SECP_SIG_LEN]),
687 )
688 .await;
689 assert!(
690 !matches!(err, TipsetSyncerError::SecpSignatureTypeInvalid(..)),
691 "the guard must not fire, got: {err}"
692 );
693 }
694
695 #[tokio::test]
697 async fn signature_type_is_not_checked_before_nv14() {
698 let chain_config = chain_config();
699 let epoch = last_epoch_before_nv14(&chain_config);
700 let err = validate(
701 chain_config,
702 epoch,
703 Signature::new_bls(vec![0; BLS_SIG_LEN]),
704 )
705 .await;
706 assert!(
707 !matches!(err, TipsetSyncerError::SecpSignatureTypeInvalid(..)),
708 "the guard must not fire, got: {err}"
709 );
710 }
711 }
712}