use std::{
sync::Arc,
time::{SystemTime, UNIX_EPOCH},
};
use crate::blocks::{Block, FullTipset, Tipset, TxMeta};
use crate::chain::ChainStore;
use crate::message::SignedMessage;
use crate::shim::message::Message;
use crate::utils::{cid::CidCborExt, db::CborStoreExt};
use cid::Cid;
use fil_actors_shared::fvm_ipld_amt::{Amtv0 as Amt, Error as IpldAmtError};
use fvm_ipld_blockstore::Blockstore;
use fvm_ipld_encoding::Error as EncodingError;
use thiserror::Error;
use crate::chain_sync::bad_block_cache::BadBlockCache;
const MAX_HEIGHT_DRIFT: u64 = 5;
#[derive(Debug, Error)]
pub enum TipsetValidationError {
#[error("Tipset has no blocks")]
NoBlocks,
#[error("Tipset has an epoch that is too large")]
EpochTooLarge,
#[error("Tipset has an insufficient weight")]
InsufficientWeight,
#[error("Tipset block = [CID = {0}] is invalid: {1}")]
InvalidBlock(Cid, String),
#[error("Tipset headers are invalid")]
InvalidRoots,
#[error("Tipset IPLD error: {0}")]
IpldAmt(String),
#[error("Block store error while validating tipset: {0}")]
Blockstore(String),
#[error("Encoding error while validating tipset: {0}")]
Encoding(EncodingError),
}
impl From<EncodingError> for Box<TipsetValidationError> {
fn from(err: EncodingError) -> Self {
Box::new(TipsetValidationError::Encoding(err))
}
}
impl From<IpldAmtError> for Box<TipsetValidationError> {
fn from(err: IpldAmtError) -> Self {
Box::new(TipsetValidationError::IpldAmt(err.to_string()))
}
}
pub struct TipsetValidator<'a>(pub &'a FullTipset);
impl<'a> TipsetValidator<'a> {
pub fn validate<DB: Blockstore>(
&self,
chainstore: Arc<ChainStore<DB>>,
bad_block_cache: Arc<BadBlockCache>,
genesis_tipset: Arc<Tipset>,
block_delay: u32,
) -> Result<(), Box<TipsetValidationError>> {
if self.0.blocks().is_empty() {
return Err(Box::new(TipsetValidationError::NoBlocks));
}
self.validate_epoch(genesis_tipset, block_delay)?;
for block in self.0.blocks() {
self.validate_msg_root(&chainstore.db, block)?;
if let Some(bad) = bad_block_cache.peek(block.cid()) {
return Err(Box::new(TipsetValidationError::InvalidBlock(
*block.cid(),
bad,
)));
}
}
Ok(())
}
pub fn validate_epoch(
&self,
genesis_tipset: Arc<Tipset>,
block_delay: u32,
) -> Result<(), Box<TipsetValidationError>> {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let max_epoch =
((now - genesis_tipset.min_timestamp()) / block_delay as u64) + MAX_HEIGHT_DRIFT;
let too_far_ahead_in_time = self.0.epoch() as u64 > max_epoch;
if too_far_ahead_in_time {
Err(Box::new(TipsetValidationError::EpochTooLarge))
} else {
Ok(())
}
}
pub fn validate_msg_root<DB: Blockstore>(
&self,
blockstore: &DB,
block: &Block,
) -> Result<(), Box<TipsetValidationError>> {
let msg_root = Self::compute_msg_root(blockstore, block.bls_msgs(), block.secp_msgs())?;
if block.header().messages != msg_root {
Err(Box::new(TipsetValidationError::InvalidRoots))
} else {
Ok(())
}
}
pub fn compute_msg_root<DB: Blockstore>(
blockstore: &DB,
bls_msgs: &[Message],
secp_msgs: &[SignedMessage],
) -> Result<Cid, Box<TipsetValidationError>> {
let bls_cids = bls_msgs
.iter()
.map(Cid::from_cbor_blake2b256)
.collect::<Result<Vec<Cid>, fvm_ipld_encoding::Error>>()?;
let secp_cids = secp_msgs
.iter()
.map(Cid::from_cbor_blake2b256)
.collect::<Result<Vec<Cid>, fvm_ipld_encoding::Error>>()?;
let bls_message_root = Amt::new_from_iter(blockstore, bls_cids)?;
let secp_message_root = Amt::new_from_iter(blockstore, secp_cids)?;
let meta = TxMeta {
bls_message_root,
secp_message_root,
};
blockstore
.put_cbor_default(&meta)
.map_err(|e| Box::new(TipsetValidationError::Blockstore(e.to_string())))
}
}
#[cfg(test)]
mod tests {
use std::convert::TryFrom;
use crate::db::MemoryDB;
use crate::message::SignedMessage;
use crate::shim::message::Message;
use crate::test_utils::construct_messages;
use crate::utils::encoding::from_slice_with_fallback;
use base64::{prelude::BASE64_STANDARD, Engine};
use cid::Cid;
use super::TipsetValidator;
#[test]
fn compute_msg_meta_given_msgs_test() {
let blockstore = MemoryDB::default();
let (bls, secp) = construct_messages();
let expected_root =
Cid::try_from("bafy2bzaceasssikoiintnok7f3sgnekfifarzobyr3r4f25sgxmn23q4c35ic")
.unwrap();
let root = TipsetValidator::compute_msg_root(&blockstore, &[bls], &[secp])
.expect("Computing message root should succeed");
assert_eq!(root, expected_root);
}
#[test]
fn empty_msg_meta_vector() {
let blockstore = MemoryDB::default();
let usm: Vec<Message> =
from_slice_with_fallback(&BASE64_STANDARD.decode("gA==").unwrap()).unwrap();
let sm: Vec<SignedMessage> =
from_slice_with_fallback(&BASE64_STANDARD.decode("gA==").unwrap()).unwrap();
assert_eq!(
TipsetValidator::compute_msg_root(&blockstore, &usm, &sm)
.expect("Computing message root should succeed")
.to_string(),
"bafy2bzacecmda75ovposbdateg7eyhwij65zklgyijgcjwynlklmqazpwlhba"
);
}
}