forest/chain_sync/
validation.rs1use std::time::{SystemTime, UNIX_EPOCH};
5
6use crate::blocks::{Block, FullTipset, Tipset, TxMeta};
7use crate::chain::ChainStore;
8use crate::message::SignedMessage;
9use crate::shim::message::Message;
10use crate::utils::{cid::CidCborExt, db::CborStoreExt};
11use cid::Cid;
12use fil_actors_shared::fvm_ipld_amt::{Amtv0 as Amt, Error as IpldAmtError};
13use fvm_ipld_blockstore::Blockstore;
14use fvm_ipld_encoding::Error as EncodingError;
15use thiserror::Error;
16
17use crate::chain_sync::bad_block_cache::BadBlockCache;
18
19const MAX_HEIGHT_DRIFT: u64 = 5;
20
21#[derive(Debug, Error)]
22pub enum TipsetValidationError {
23 #[error("Tipset has no blocks")]
24 NoBlocks,
25 #[error("Tipset has an epoch that is too large")]
26 EpochTooLarge,
27 #[error("Tipset has an insufficient weight")]
28 InsufficientWeight,
29 #[error("Tipset block = [CID = {0}] is invalid")]
30 InvalidBlock(Cid),
31 #[error("Tipset headers are invalid")]
32 InvalidRoots,
33 #[error("Tipset IPLD error: {0}")]
34 IpldAmt(String),
35 #[error("Block store error while validating tipset: {0}")]
36 Blockstore(String),
37 #[error("Encoding error while validating tipset: {0}")]
38 Encoding(EncodingError),
39}
40
41impl From<EncodingError> for TipsetValidationError {
42 fn from(err: EncodingError) -> Self {
43 TipsetValidationError::Encoding(err)
44 }
45}
46
47impl From<IpldAmtError> for TipsetValidationError {
48 fn from(err: IpldAmtError) -> Self {
49 TipsetValidationError::IpldAmt(err.to_string())
50 }
51}
52
53pub struct TipsetValidator<'a>(pub &'a FullTipset);
54
55impl TipsetValidator<'_> {
56 pub fn validate<DB: Blockstore>(
57 &self,
58 chainstore: &ChainStore<DB>,
59 bad_block_cache: Option<&BadBlockCache>,
60 genesis_tipset: &Tipset,
61 block_delay: u32,
62 ) -> Result<(), TipsetValidationError> {
63 if self.0.blocks().is_empty() {
65 return Err(TipsetValidationError::NoBlocks);
66 }
67
68 self.validate_epoch(genesis_tipset, block_delay)?;
70
71 for block in self.0.blocks() {
76 Self::validate_msg_root(chainstore.blockstore(), block)?;
77 if let Some(bad_block_cache) = bad_block_cache
78 && bad_block_cache.peek(block.cid()).is_some()
79 {
80 return Err(TipsetValidationError::InvalidBlock(*block.cid()));
81 }
82 }
83
84 Ok(())
85 }
86
87 pub fn validate_epoch(
88 &self,
89 genesis_tipset: &Tipset,
90 block_delay: u32,
91 ) -> Result<(), TipsetValidationError> {
92 let now = SystemTime::now()
93 .duration_since(UNIX_EPOCH)
94 .unwrap()
95 .as_secs();
96 let max_epoch =
97 ((now - genesis_tipset.min_timestamp()) / block_delay as u64) + MAX_HEIGHT_DRIFT;
98 let too_far_ahead_in_time = self.0.epoch() as u64 > max_epoch;
99 if too_far_ahead_in_time {
100 Err(TipsetValidationError::EpochTooLarge)
101 } else {
102 Ok(())
103 }
104 }
105
106 pub fn validate_msg_root<DB: Blockstore>(
107 blockstore: &DB,
108 block: &Block,
109 ) -> Result<(), TipsetValidationError> {
110 let msg_root = Self::compute_msg_root(blockstore, block.bls_msgs(), block.secp_msgs())?;
111 if block.header().messages != msg_root {
112 Err(TipsetValidationError::InvalidRoots)
113 } else {
114 Ok(())
115 }
116 }
117
118 pub fn compute_msg_root<DB: Blockstore>(
119 blockstore: &DB,
120 bls_msgs: &[Message],
121 secp_msgs: &[SignedMessage],
122 ) -> Result<Cid, TipsetValidationError> {
123 let bls_cids = bls_msgs
125 .iter()
126 .map(Cid::from_cbor_blake2b256)
127 .collect::<Result<Vec<Cid>, fvm_ipld_encoding::Error>>()?;
128 let secp_cids = secp_msgs
129 .iter()
130 .map(Cid::from_cbor_blake2b256)
131 .collect::<Result<Vec<Cid>, fvm_ipld_encoding::Error>>()?;
132
133 let bls_message_root = Amt::new_from_iter(blockstore, bls_cids)?;
135 let secp_message_root = Amt::new_from_iter(blockstore, secp_cids)?;
136 let meta = TxMeta {
137 bls_message_root,
138 secp_message_root,
139 };
140
141 blockstore
143 .put_cbor_default(&meta)
144 .map_err(|e| TipsetValidationError::Blockstore(e.to_string()))
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use std::convert::TryFrom;
151
152 use crate::db::MemoryDB;
153 use crate::message::SignedMessage;
154 use crate::shim::message::Message;
155 use crate::test_utils::construct_messages;
156 use crate::utils::encoding::from_slice_with_fallback;
157 use base64::{Engine, prelude::BASE64_STANDARD};
158 use cid::Cid;
159
160 use super::TipsetValidator;
161
162 #[test]
163 fn compute_msg_meta_given_msgs_test() {
164 let blockstore = MemoryDB::default();
165
166 let (bls, secp) = construct_messages();
167
168 let expected_root =
169 Cid::try_from("bafy2bzaceasssikoiintnok7f3sgnekfifarzobyr3r4f25sgxmn23q4c35ic")
170 .unwrap();
171
172 let root = TipsetValidator::compute_msg_root(&blockstore, &[bls], &[secp])
173 .expect("Computing message root should succeed");
174 assert_eq!(root, expected_root);
175 }
176
177 #[test]
178 fn empty_msg_meta_vector() {
179 let blockstore = MemoryDB::default();
180 let usm: Vec<Message> =
181 from_slice_with_fallback(&BASE64_STANDARD.decode("gA==").unwrap()).unwrap();
182 let sm: Vec<SignedMessage> =
183 from_slice_with_fallback(&BASE64_STANDARD.decode("gA==").unwrap()).unwrap();
184
185 assert_eq!(
186 TipsetValidator::compute_msg_root(&blockstore, &usm, &sm)
187 .expect("Computing message root should succeed")
188 .to_string(),
189 "bafy2bzacecmda75ovposbdateg7eyhwij65zklgyijgcjwynlklmqazpwlhba"
190 );
191 }
192}