From 9d76a71562f700f4531cd4bfd786ea95d022c67f Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Mon, 28 Oct 2024 16:23:06 +0800 Subject: [PATCH] fix(f3): validate tipset before resetting the chain head (#4931) --- scripts/tests/api_compare/filter-list | 2 ++ src/chain/store/chain_store.rs | 5 +++ src/chain_sync/chain_muxer.rs | 8 ++--- src/chain_sync/mod.rs | 3 +- src/chain_sync/validation.rs | 46 ++++++++++++--------------- src/rpc/error.rs | 1 + src/rpc/methods/f3.rs | 38 ++++++++++++++-------- src/rpc/methods/sync.rs | 10 +++--- 8 files changed, 64 insertions(+), 49 deletions(-) diff --git a/scripts/tests/api_compare/filter-list b/scripts/tests/api_compare/filter-list index 86a63a4f6529..72b86badc3f2 100644 --- a/scripts/tests/api_compare/filter-list +++ b/scripts/tests/api_compare/filter-list @@ -8,3 +8,5 @@ !Filecoin.EthGetTransactionReceipt # TODO(elmattic): https://github.com/ChainSafe/forest/issues/4851 !Filecoin.EthGetLogs +# Enable after Lotus image is upgraded to a release in which F3IntitialPowerTable is hard coded. +!Filecoin.F3GetManifest diff --git a/src/chain/store/chain_store.rs b/src/chain/store/chain_store.rs index f75daea6637e..d66f48535bb5 100644 --- a/src/chain/store/chain_store.rs +++ b/src/chain/store/chain_store.rs @@ -241,6 +241,11 @@ where .expect("failed to load heaviest tipset") } + /// Returns the genesis tipset. + pub fn genesis_tipset(&self) -> Tipset { + Tipset::from(self.genesis_block_header()) + } + /// Returns a reference to the publisher of head changes. pub fn publisher(&self) -> &Publisher { &self.publisher diff --git a/src/chain_sync/chain_muxer.rs b/src/chain_sync/chain_muxer.rs index 182c0f2343ac..b15070c8bb48 100644 --- a/src/chain_sync/chain_muxer.rs +++ b/src/chain_sync/chain_muxer.rs @@ -60,7 +60,7 @@ pub enum ChainMuxerError { #[error("Tipset range syncer error: {0}")] TipsetRangeSyncer(#[from] TipsetRangeSyncerError), #[error("Tipset validation error: {0}")] - TipsetValidator(#[from] Box), + TipsetValidator(#[from] TipsetValidationError), #[error("Sending tipset on channel failed: {0}")] TipsetChannelSend(String), #[error("Receiving p2p network event failed: {0}")] @@ -512,9 +512,9 @@ where // Validate tipset if let Err(why) = TipsetValidator(&tipset).validate( - chain_store.clone(), - bad_block_cache.clone(), - genesis.clone(), + &chain_store, + Some(&bad_block_cache), + &genesis, block_delay, ) { metrics::INVALID_TIPSET_TOTAL.inc(); diff --git a/src/chain_sync/mod.rs b/src/chain_sync/mod.rs index a649f29de8a8..683f0961dc2a 100644 --- a/src/chain_sync/mod.rs +++ b/src/chain_sync/mod.rs @@ -10,11 +10,10 @@ mod sync_state; mod tipset_syncer; mod validation; -pub use validation::TipsetValidator; - pub use self::{ bad_block_cache::BadBlockCache, chain_muxer::{ChainMuxer, SyncConfig}, consensus::collect_errs, sync_state::{SyncStage, SyncState}, + validation::{TipsetValidationError, TipsetValidator}, }; diff --git a/src/chain_sync/validation.rs b/src/chain_sync/validation.rs index 315ffd97a334..f41737be36cd 100644 --- a/src/chain_sync/validation.rs +++ b/src/chain_sync/validation.rs @@ -1,10 +1,7 @@ // Copyright 2019-2024 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT -use std::{ - sync::Arc, - time::{SystemTime, UNIX_EPOCH}, -}; +use std::time::{SystemTime, UNIX_EPOCH}; use crate::blocks::{Block, FullTipset, Tipset, TxMeta}; use crate::chain::ChainStore; @@ -41,15 +38,15 @@ pub enum TipsetValidationError { Encoding(EncodingError), } -impl From for Box { +impl From for TipsetValidationError { fn from(err: EncodingError) -> Self { - Box::new(TipsetValidationError::Encoding(err)) + TipsetValidationError::Encoding(err) } } -impl From for Box { +impl From for TipsetValidationError { fn from(err: IpldAmtError) -> Self { - Box::new(TipsetValidationError::IpldAmt(err.to_string())) + TipsetValidationError::IpldAmt(err.to_string()) } } @@ -58,14 +55,14 @@ pub struct TipsetValidator<'a>(pub &'a FullTipset); impl<'a> TipsetValidator<'a> { pub fn validate( &self, - chainstore: Arc>, - bad_block_cache: Arc, - genesis_tipset: Arc, + chainstore: &ChainStore, + bad_block_cache: Option<&BadBlockCache>, + genesis_tipset: &Tipset, block_delay: u32, - ) -> Result<(), Box> { + ) -> Result<(), TipsetValidationError> { // No empty blocks if self.0.blocks().is_empty() { - return Err(Box::new(TipsetValidationError::NoBlocks)); + return Err(TipsetValidationError::NoBlocks); } // Tipset epoch must not be behind current max @@ -77,11 +74,10 @@ impl<'a> TipsetValidator<'a> { // previously been seen in the bad blocks cache 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, - ))); + if let Some(bad_block_cache) = bad_block_cache { + if let Some(bad) = bad_block_cache.peek(block.cid()) { + return Err(TipsetValidationError::InvalidBlock(*block.cid(), bad)); + } } } @@ -90,9 +86,9 @@ impl<'a> TipsetValidator<'a> { pub fn validate_epoch( &self, - genesis_tipset: Arc, + genesis_tipset: &Tipset, block_delay: u32, - ) -> Result<(), Box> { + ) -> Result<(), TipsetValidationError> { let now = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() @@ -101,7 +97,7 @@ impl<'a> TipsetValidator<'a> { ((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)) + Err(TipsetValidationError::EpochTooLarge) } else { Ok(()) } @@ -111,10 +107,10 @@ impl<'a> TipsetValidator<'a> { &self, blockstore: &DB, block: &Block, - ) -> Result<(), Box> { + ) -> Result<(), 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)) + Err(TipsetValidationError::InvalidRoots) } else { Ok(()) } @@ -124,7 +120,7 @@ impl<'a> TipsetValidator<'a> { blockstore: &DB, bls_msgs: &[Message], secp_msgs: &[SignedMessage], - ) -> Result> { + ) -> Result { // Generate message CIDs let bls_cids = bls_msgs .iter() @@ -146,7 +142,7 @@ impl<'a> TipsetValidator<'a> { // Store message roots and receive meta_root CID blockstore .put_cbor_default(&meta) - .map_err(|e| Box::new(TipsetValidationError::Blockstore(e.to_string()))) + .map_err(|e| TipsetValidationError::Blockstore(e.to_string())) } } diff --git a/src/rpc/error.rs b/src/rpc/error.rs index bc20343ccf6f..d9d1e007d5af 100644 --- a/src/rpc/error.rs +++ b/src/rpc/error.rs @@ -116,6 +116,7 @@ from2internal! { base64::DecodeError, cid::multibase::Error, crate::chain::store::Error, + crate::chain_sync::TipsetValidationError, crate::key_management::Error, crate::libp2p::ParseError, crate::message_pool::Error, diff --git a/src/rpc/methods/f3.rs b/src/rpc/methods/f3.rs index e679898b2bc1..054ed9af36f1 100644 --- a/src/rpc/methods/f3.rs +++ b/src/rpc/methods/f3.rs @@ -14,7 +14,9 @@ pub use self::types::F3LeaseManager; use self::{types::*, util::*}; use super::wallet::WalletSign; use crate::{ + blocks::Tipset, chain::index::ResolveNullTipset, + chain_sync::TipsetValidator, libp2p::{NetRPCMethods, NetworkMessage}, lotus_json::HasLotusJson as _, rpc::{types::ApiTipsetKey, ApiPaths, Ctx, Permission, RpcMethod, ServerError}, @@ -481,19 +483,13 @@ impl RpcMethod<1> for Finalize { let tsk = f3_tsk.try_into()?; let finalized_ts = match ctx.chain_index().load_tipset(&tsk)? { Some(ts) => ts, - None => { - let ts = ctx - .sync_network_context - .chain_exchange_headers(None, &tsk, NonZeroU64::new(1).expect("Infallible")) - .await? - .first() - .cloned() - .with_context(|| { - format!("failed to get tipset via chain exchange. tsk: {tsk}") - })?; - ctx.chain_store().put_tipset(&ts)?; - ts - } + None => ctx + .sync_network_context + .chain_exchange_headers(None, &tsk, NonZeroU64::new(1).expect("Infallible")) + .await? + .first() + .cloned() + .with_context(|| format!("failed to get tipset via chain exchange. tsk: {tsk}"))?, }; tracing::info!( "F3 finalized tsk {} at epoch {}", @@ -516,6 +512,22 @@ impl RpcMethod<1> for Finalize { finalized_ts.key(), finalized_ts.epoch() ); + let fts = ctx + .sync_network_context + .chain_exchange_fts(None, &tsk) + .await?; + for block in fts.blocks() { + block.persist(ctx.store())?; + } + let validator = TipsetValidator(&fts); + validator.validate( + ctx.chain_store(), + None, + &ctx.chain_store().genesis_tipset(), + ctx.chain_config().block_delay_secs, + )?; + let ts = Arc::new(Tipset::from(fts)); + ctx.chain_store().put_tipset(&ts)?; ctx.chain_store().set_heaviest_tipset(finalized_ts)?; } Ok(()) diff --git a/src/rpc/methods/sync.rs b/src/rpc/methods/sync.rs index 0ced1077b141..14e7730c2c25 100644 --- a/src/rpc/methods/sync.rs +++ b/src/rpc/methods/sync.rs @@ -1,7 +1,7 @@ // Copyright 2019-2024 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT -use crate::blocks::{Block, FullTipset, GossipBlock, Tipset}; +use crate::blocks::{Block, FullTipset, GossipBlock}; use crate::libp2p::{IdentTopic, NetworkMessage, PUBSUB_BLOCK_STR}; use crate::lotus_json::{lotus_json_with_self, LotusJson}; use crate::rpc::{ApiPaths, Ctx, Permission, RpcMethod, ServerError}; @@ -100,13 +100,13 @@ impl RpcMethod<1> for SyncSubmitBlock { secp_messages, }; let ts = FullTipset::from(block); - let genesis_ts = Arc::new(Tipset::from(ctx.chain_store().genesis_block_header())); + let genesis_ts = ctx.chain_store().genesis_tipset(); TipsetValidator(&ts) .validate( - ctx.chain_store().clone(), - ctx.bad_blocks.clone(), - genesis_ts, + ctx.chain_store(), + Some(&ctx.bad_blocks), + &genesis_ts, ctx.chain_config().block_delay_secs, ) .context("failed to validate the tipset")?;