diff --git a/base_layer/core/src/base_node/sync/block_sync/error.rs b/base_layer/core/src/base_node/sync/block_sync/error.rs index 6e4e772e34..ed7d98288b 100644 --- a/base_layer/core/src/base_node/sync/block_sync/error.rs +++ b/base_layer/core/src/base_node/sync/block_sync/error.rs @@ -34,8 +34,6 @@ pub enum BlockSyncError { RpcRequestError(#[from] RpcStatus), #[error("Chain storage error: {0}")] ChainStorageError(#[from] ChainStorageError), - #[error("Peer sent invalid block body: {0}")] - ReceivedInvalidBlockBody(String), #[error("Peer sent a block that did not form a chain. Expected hash = {expected}, got = {got}")] PeerSentBlockThatDidNotFormAChain { expected: String, got: String }, #[error("Connectivity Error: {0}")] @@ -48,4 +46,6 @@ pub enum BlockSyncError { FailedToBan(ConnectivityError), #[error("Failed to construct valid chain block")] FailedToConstructChainBlock, + #[error("Peer violated the block sync protocol: {0}")] + ProtocolViolation(String), } diff --git a/base_layer/core/src/base_node/sync/block_sync/synchronizer.rs b/base_layer/core/src/base_node/sync/block_sync/synchronizer.rs index b476f4078f..5b4e1d29f2 100644 --- a/base_layer/core/src/base_node/sync/block_sync/synchronizer.rs +++ b/base_layer/core/src/base_node/sync/block_sync/synchronizer.rs @@ -26,7 +26,7 @@ use crate::{ sync::{hooks::Hooks, rpc, SyncPeer}, BlockSyncConfig, }, - blocks::{Block, ChainBlock}, + blocks::{Block, BlockValidationError, ChainBlock}, chain_storage::{async_db::AsyncBlockchainDb, BlockchainBackend}, proto::base_node::SyncBlocksRequest, tari_utilities::{hex::Hex, Hashable}, @@ -97,7 +97,29 @@ impl BlockSynchronizer { Ok(()) }, Err(err @ BlockSyncError::ValidationError(ValidationError::AsyncTaskFailed(_))) => Err(err), - Err(err @ BlockSyncError::ValidationError(_)) | Err(err @ BlockSyncError::ReceivedInvalidBlockBody(_)) => { + Err(BlockSyncError::ValidationError(err)) => { + match &err { + ValidationError::BlockHeaderError(_) => {}, + ValidationError::BlockError(BlockValidationError::MismatchedMmrRoots) | + ValidationError::BadBlockFound { .. } | + ValidationError::BlockError(BlockValidationError::MismatchedMmrSize { .. }) => { + let num_cleared = self.db.clear_all_pending_headers().await?; + warn!( + target: LOG_TARGET, + "Cleared {} incomplete headers from bad chain", num_cleared + ); + }, + _ => {}, + } + warn!( + target: LOG_TARGET, + "Banning peer because provided block failed validation: {}", err + ); + self.ban_peer(node_id, &err).await?; + Err(err.into()) + }, + Err(err @ BlockSyncError::ProtocolViolation(_)) => { + warn!(target: LOG_TARGET, "Banning peer: {}", err); self.ban_peer(node_id, &err).await?; Err(err) }, @@ -167,9 +189,10 @@ impl BlockSynchronizer { .fetch_chain_header_by_block_hash(block.hash.clone()) .await? .ok_or_else(|| { - BlockSyncError::ReceivedInvalidBlockBody("Peer sent hash for block header we do not have".into()) + BlockSyncError::ProtocolViolation("Peer sent hash for block header we do not have".into()) })?; + let current_height = header.height(); let header_hash = header.hash().clone(); if header.header().prev_hash != prev_hash { @@ -184,13 +207,13 @@ impl BlockSynchronizer { let body = block .body .map(AggregateBody::try_from) - .ok_or_else(|| BlockSyncError::ReceivedInvalidBlockBody("Block body was empty".to_string()))? - .map_err(BlockSyncError::ReceivedInvalidBlockBody)?; + .ok_or_else(|| BlockSyncError::ProtocolViolation("Block body was empty".to_string()))? + .map_err(BlockSyncError::ProtocolViolation)?; debug!( target: LOG_TARGET, "Validating block body #{} (PoW = {}, {})", - header.height(), + current_height, header.header().pow_algo(), body.to_counts_string(), ); @@ -198,7 +221,26 @@ impl BlockSynchronizer { let timer = Instant::now(); let (header, header_accum_data) = header.into_parts(); - let block = self.block_validator.validate_body(Block::new(header, body)).await?; + let block = match self.block_validator.validate_body(Block::new(header, body)).await { + Ok(block) => block, + Err(err @ ValidationError::BadBlockFound { .. }) | + Err(err @ ValidationError::FatalStorageError(_)) | + Err(err @ ValidationError::AsyncTaskFailed(_)) | + Err(err @ ValidationError::CustomError(_)) => return Err(err.into()), + Err(err) => { + // Add to bad blocks + if let Err(err) = self + .db + .write_transaction() + .insert_bad_block(header_hash, current_height) + .commit() + .await + { + error!(target: LOG_TARGET, "Failed to insert bad block: {}", err); + } + return Err(err.into()); + }, + }; let block = ChainBlock::try_construct(Arc::new(block), header_accum_data) .map(Arc::new) diff --git a/base_layer/core/src/base_node/sync/header_sync/validator.rs b/base_layer/core/src/base_node/sync/header_sync/validator.rs index 601e372469..efd0d8617f 100644 --- a/base_layer/core/src/base_node/sync/header_sync/validator.rs +++ b/base_layer/core/src/base_node/sync/header_sync/validator.rs @@ -30,6 +30,7 @@ use crate::{ tari_utilities::{epoch_time::EpochTime, hash::Hashable, hex::Hex}, validation::helpers::{ check_header_timestamp_greater_than_median, + check_not_bad_block, check_pow_data, check_target_difficulty, check_timestamp_ftl, @@ -138,7 +139,13 @@ impl BlockHeaderSyncValidator { ); let achieved_target = check_target_difficulty(&header, target_difficulty, &self.randomx_factory)?; - check_pow_data(&header, &self.consensus_rules, &*self.db.inner().db_read_access()?)?; + let block_hash = header.hash(); + + { + let txn = self.db.inner().db_read_access()?; + check_not_bad_block(&*txn, &block_hash)?; + check_pow_data(&header, &self.consensus_rules, &*txn)?; + } // Header is valid, add this header onto the validation state for the next round // Mutable borrow done later in the function to allow multiple immutable borrows before this line. This has @@ -159,7 +166,7 @@ impl BlockHeaderSyncValidator { state.target_difficulties.add_back(&header, target_difficulty); let accumulated_data = BlockHeaderAccumulatedData::builder(&state.previous_accum) - .with_hash(header.hash()) + .with_hash(block_hash) .with_achieved_target_difficulty(achieved_target) .with_total_kernel_offset(header.total_kernel_offset.clone()) .build()?; diff --git a/base_layer/core/src/chain_storage/async_db.rs b/base_layer/core/src/chain_storage/async_db.rs index 116e87e8dc..9437431e03 100644 --- a/base_layer/core/src/chain_storage/async_db.rs +++ b/base_layer/core/src/chain_storage/async_db.rs @@ -207,6 +207,8 @@ impl AsyncBlockchainDb { make_async_fn!(fetch_last_header() -> BlockHeader, "fetch_last_header"); + make_async_fn!(clear_all_pending_headers() -> usize, "clear_all_pending_headers"); + make_async_fn!(fetch_last_chain_header() -> ChainHeader, "fetch_last_chain_header"); make_async_fn!(fetch_tip_header() -> ChainHeader, "fetch_tip_header"); @@ -222,6 +224,8 @@ impl AsyncBlockchainDb { make_async_fn!(block_exists(block_hash: BlockHash) -> bool, "block_exists"); + make_async_fn!(bad_block_exists(block_hash: BlockHash) -> bool, "bad_block_exists"); + make_async_fn!(fetch_block(height: u64) -> HistoricalBlock, "fetch_block"); make_async_fn!(fetch_blocks>(bounds: T) -> Vec, "fetch_blocks"); @@ -372,6 +376,11 @@ impl<'a, B: BlockchainBackend + 'static> AsyncDbTransaction<'a, B> { self } + pub fn insert_bad_block(&mut self, hash: HashOutput, height: u64) -> &mut Self { + self.transaction.insert_bad_block(hash, height); + self + } + pub fn prune_output_at_positions(&mut self, positions: Vec) -> &mut Self { self.transaction.prune_outputs_at_positions(positions); self diff --git a/base_layer/core/src/chain_storage/blockchain_backend.rs b/base_layer/core/src/chain_storage/blockchain_backend.rs index ede1ca87c2..63bc5f542d 100644 --- a/base_layer/core/src/chain_storage/blockchain_backend.rs +++ b/base_layer/core/src/chain_storage/blockchain_backend.rs @@ -132,6 +132,10 @@ pub trait BlockchainBackend: Send + Sync { fn orphan_count(&self) -> Result; /// Returns the stored header with the highest corresponding height. fn fetch_last_header(&self) -> Result; + + /// Clear all headers that are beyond the current height of longest chain, returning the number of headers that were + /// deleted. + fn clear_all_pending_headers(&self) -> Result; /// Returns the stored header and accumulated data with the highest height. fn fetch_last_chain_header(&self) -> Result; /// Returns the stored header with the highest corresponding height. @@ -178,4 +182,7 @@ pub trait BlockchainBackend: Send + Sync { &self, mmr_positions: Vec, ) -> Result>, ChainStorageError>; + + /// Check if a block hash is in the bad block list + fn bad_block_exists(&self, block_hash: HashOutput) -> Result; } diff --git a/base_layer/core/src/chain_storage/blockchain_database.rs b/base_layer/core/src/chain_storage/blockchain_database.rs index 07cd9c20b3..d88f795de0 100644 --- a/base_layer/core/src/chain_storage/blockchain_database.rs +++ b/base_layer/core/src/chain_storage/blockchain_database.rs @@ -85,7 +85,6 @@ use crate::{ HeaderValidation, OrphanValidation, PostOrphanBodyValidation, - ValidationError, }, }; @@ -860,6 +859,11 @@ where B: BlockchainBackend Ok(()) } + pub fn clear_all_pending_headers(&self) -> Result { + let db = self.db_write_access()?; + db.clear_all_pending_headers() + } + /// Clean out the entire orphan pool pub fn cleanup_all_orphans(&self) -> Result<(), ChainStorageError> { let mut db = self.db_write_access()?; @@ -962,6 +966,12 @@ where B: BlockchainBackend Ok(db.contains(&DbKey::BlockHash(hash.clone()))? || db.contains(&DbKey::OrphanBlock(hash))?) } + /// Returns true if this block exists in the chain, or is orphaned. + pub fn bad_block_exists(&self, hash: BlockHash) -> Result { + let db = self.db_read_access()?; + db.bad_block_exists(hash) + } + /// Atomically commit the provided transaction to the database backend. This function does not update the metadata. pub fn commit(&self, txn: DbTransaction) -> Result<(), ChainStorageError> { let mut db = self.db_write_access()?; @@ -1276,10 +1286,13 @@ fn insert_best_block(txn: &mut DbTransaction, block: Arc) -> Result< block_hash.to_hex() ); if block.header().pow_algo() == PowAlgorithm::Monero { - let monero_seed = MoneroPowData::from_header(block.header()) - .map_err(|e| ValidationError::CustomError(e.to_string()))? - .randomx_key; - txn.insert_monero_seed_height(monero_seed.to_vec(), block.height()); + let monero_header = + MoneroPowData::from_header(block.header()).map_err(|e| ChainStorageError::InvalidArguments { + func: "insert_best_block", + arg: "block", + message: format!("block contained invalid or malformed monero PoW data: {}", e), + })?; + txn.insert_monero_seed_height(monero_header.randomx_key.to_vec(), block.height()); } let height = block.height(); diff --git a/base_layer/core/src/chain_storage/db_transaction.rs b/base_layer/core/src/chain_storage/db_transaction.rs index bfdd480033..b8d7b83229 100644 --- a/base_layer/core/src/chain_storage/db_transaction.rs +++ b/base_layer/core/src/chain_storage/db_transaction.rs @@ -184,6 +184,15 @@ impl DbTransaction { self } + /// Inserts a block hash into the bad block list + pub fn insert_bad_block(&mut self, block_hash: HashOutput, height: u64) -> &mut Self { + self.operations.push(WriteOperation::InsertBadBlock { + hash: block_hash, + height, + }); + self + } + /// Stores an orphan block. No checks are made as to whether this is actually an orphan. That responsibility lies /// with the calling function. /// The transaction will rollback and write will return an error if the orphan already exists. @@ -295,6 +304,10 @@ pub enum WriteOperation { witness_hash: HashOutput, mmr_position: u32, }, + InsertBadBlock { + hash: HashOutput, + height: u64, + }, DeleteHeader(u64), DeleteOrphan(HashOutput), DeleteBlock(HashOutput), @@ -414,6 +427,7 @@ impl fmt::Display for WriteOperation { SetPrunedHeight { height, .. } => write!(f, "Set pruned height to {}", height), DeleteHeader(height) => write!(f, "Delete header at height: {}", height), DeleteOrphan(hash) => write!(f, "Delete orphan with hash: {}", hash.to_hex()), + InsertBadBlock { hash, height } => write!(f, "Insert bad block #{} {}", height, hash.to_hex()), SetHorizonData { .. } => write!(f, "Set horizon data"), } } diff --git a/base_layer/core/src/chain_storage/lmdb_db/lmdb.rs b/base_layer/core/src/chain_storage/lmdb_db/lmdb.rs index 4b3cf5f2d4..783400b1c8 100644 --- a/base_layer/core/src/chain_storage/lmdb_db/lmdb.rs +++ b/base_layer/core/src/chain_storage/lmdb_db/lmdb.rs @@ -25,7 +25,7 @@ use lmdb_zero::{ del, error::{self, LmdbResultExt}, put, - traits::{AsLmdbBytes, FromLmdbBytes}, + traits::{AsLmdbBytes, CreateCursor, FromLmdbBytes}, ConstTransaction, Cursor, CursorIter, @@ -397,7 +397,7 @@ where Ok(result) } -/// Fetches all the size of all key/values in the given DB. Returns the number of entries, the total size of all the +/// Fetches the size of all key/values in the given DB. Returns the number of entries, the total size of all the /// keys and values in bytes. pub fn fetch_db_entry_sizes(txn: &ConstTransaction<'_>, db: &Database) -> Result<(u64, u64, u64), ChainStorageError> { let access = txn.access(); @@ -412,3 +412,40 @@ pub fn fetch_db_entry_sizes(txn: &ConstTransaction<'_>, db: &Database) -> Result } Ok((num_entries, total_key_size, total_value_size)) } + +pub fn lmdb_delete_each_where( + txn: &WriteTransaction<'_>, + db: &Database, + mut predicate: F, +) -> Result +where + K: FromLmdbBytes + ?Sized, + V: DeserializeOwned, + F: FnMut(&K, V) -> Option, +{ + let mut cursor = txn.cursor(db)?; + let mut access = txn.access(); + let mut num_deleted = 0; + while let Some((k, v)) = cursor.next::(&access).to_opt()? { + match deserialize(v) { + Ok(v) => match predicate(k, v) { + Some(true) => { + cursor.del(&mut access, del::Flags::empty())?; + num_deleted += 1; + }, + Some(false) => continue, + None => { + break; + }, + }, + Err(e) => { + error!( + target: LOG_TARGET, + "Could not could not deserialize value from lmdb: {:?}", e + ); + return Err(ChainStorageError::AccessError(e.to_string())); + }, + } + } + Ok(num_deleted) +} diff --git a/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs b/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs index 6a8908ad75..fd8bc37be5 100644 --- a/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs +++ b/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs @@ -50,6 +50,7 @@ use crate::{ lmdb::{ fetch_db_entry_sizes, lmdb_delete, + lmdb_delete_each_where, lmdb_delete_key_value, lmdb_delete_keys_starting_with, lmdb_exists, @@ -118,6 +119,7 @@ const LMDB_DB_MONERO_SEED_HEIGHT: &str = "monero_seed_height"; const LMDB_DB_ORPHAN_HEADER_ACCUMULATED_DATA: &str = "orphan_accumulated_data"; const LMDB_DB_ORPHAN_CHAIN_TIPS: &str = "orphan_chain_tips"; const LMDB_DB_ORPHAN_PARENT_MAP_INDEX: &str = "orphan_parent_map_index"; +const LMDB_DB_BAD_BLOCK_LIST: &str = "bad_blocks"; pub fn create_lmdb_database>(path: P, config: LMDBConfig) -> Result { let flags = db::CREATE; @@ -129,7 +131,7 @@ pub fn create_lmdb_database>(path: P, config: LMDBConfig) -> Resu let lmdb_store = LMDBBuilder::new() .set_path(path) .set_env_config(config) - .set_max_number_of_databases(20) + .set_max_number_of_databases(40) .add_database(LMDB_DB_METADATA, flags | db::INTEGERKEY) .add_database(LMDB_DB_HEADERS, flags | db::INTEGERKEY) .add_database(LMDB_DB_HEADER_ACCUMULATED_DATA, flags | db::INTEGERKEY) @@ -150,6 +152,7 @@ pub fn create_lmdb_database>(path: P, config: LMDBConfig) -> Resu .add_database(LMDB_DB_MONERO_SEED_HEIGHT, flags) .add_database(LMDB_DB_ORPHAN_CHAIN_TIPS, flags) .add_database(LMDB_DB_ORPHAN_PARENT_MAP_INDEX, flags | db::DUPSORT) + .add_database(LMDB_DB_BAD_BLOCK_LIST, flags) .build() .map_err(|err| ChainStorageError::CriticalError(format!("Could not create LMDB store:{}", err)))?; debug!(target: LOG_TARGET, "LMDB database creation successful"); @@ -180,6 +183,7 @@ pub struct LMDBDatabase { orphan_header_accumulated_data_db: DatabaseRef, orphan_chain_tips_db: DatabaseRef, orphan_parent_map_index: DatabaseRef, + bad_blocks: DatabaseRef, _file_lock: Arc, } @@ -211,6 +215,7 @@ impl LMDBDatabase { monero_seed_height_db: get_database(&store, LMDB_DB_MONERO_SEED_HEIGHT)?, orphan_chain_tips_db: get_database(&store, LMDB_DB_ORPHAN_CHAIN_TIPS)?, orphan_parent_map_index: get_database(&store, LMDB_DB_ORPHAN_PARENT_MAP_INDEX)?, + bad_blocks: get_database(&store, LMDB_DB_BAD_BLOCK_LIST)?, env, env_config: store.env_config(), _file_lock: Arc::new(file_lock), @@ -397,6 +402,9 @@ impl LMDBDatabase { MetadataValue::HorizonData(horizon_data.clone()), )?; }, + InsertBadBlock { hash, height } => { + self.insert_bad_block_and_cleanup(&write_txn, hash, *height)?; + }, } } write_txn.commit()?; @@ -404,7 +412,7 @@ impl LMDBDatabase { Ok(()) } - fn all_dbs(&self) -> [(&'static str, &DatabaseRef); 20] { + fn all_dbs(&self) -> [(&'static str, &DatabaseRef); 21] { [ ("metadata_db", &self.metadata_db), ("headers_db", &self.headers_db), @@ -432,6 +440,7 @@ impl LMDBDatabase { ("monero_seed_height_db", &self.monero_seed_height_db), ("orphan_chain_tips_db", &self.orphan_chain_tips_db), ("orphan_parent_map_index", &self.orphan_parent_map_index), + ("bad_blocks", &self.bad_blocks), ] } @@ -1272,6 +1281,31 @@ impl LMDBDatabase { fn fetch_last_header_in_txn(&self, txn: &ConstTransaction<'_>) -> Result, ChainStorageError> { lmdb_last(txn, &self.headers_db) } + + fn insert_bad_block_and_cleanup( + &self, + txn: &WriteTransaction<'_>, + hash: &HashOutput, + height: u64, + ) -> Result<(), ChainStorageError> { + const CLEAN_BAD_BLOCKS_BEFORE_REL_HEIGHT: u64 = 10000; + + lmdb_replace(txn, &self.bad_blocks, hash, &height)?; + // Clean up bad blocks that are far from the tip + let metadata = fetch_metadata(&*txn, &self.metadata_db)?; + let deleted_before_height = metadata + .height_of_longest_chain() + .saturating_sub(CLEAN_BAD_BLOCKS_BEFORE_REL_HEIGHT); + if deleted_before_height == 0 { + return Ok(()); + } + + let num_deleted = + lmdb_delete_each_where::<[u8], u64, _>(txn, &self.bad_blocks, |_, v| Some(v < deleted_before_height))?; + debug!(target: LOG_TARGET, "Cleaned out {} stale bad blocks", num_deleted); + + Ok(()) + } } pub fn create_recovery_lmdb_database>(path: P) -> Result<(), ChainStorageError> { @@ -2050,6 +2084,37 @@ impl BlockchainBackend for LMDBDatabase { }) .collect() } + + fn bad_block_exists(&self, block_hash: HashOutput) -> Result { + let txn = self.read_transaction()?; + lmdb_exists(&txn, &self.bad_blocks, &block_hash) + } + + fn clear_all_pending_headers(&self) -> Result { + let txn = self.write_transaction()?; + let last_header = match self.fetch_last_header_in_txn(&txn)? { + Some(h) => h, + None => { + return Ok(0); + }, + }; + let metadata = fetch_metadata(&txn, &self.metadata_db)?; + + if metadata.height_of_longest_chain() == last_header.height { + return Ok(0); + } + + let start = metadata.height_of_longest_chain() + 1; + let end = last_header.height; + + let mut num_deleted = 0; + for h in (start..=end).rev() { + self.delete_header(&txn, h)?; + num_deleted += 1; + } + txn.commit()?; + Ok(num_deleted) + } } // Fetch the chain metadata diff --git a/base_layer/core/src/chain_storage/tests/blockchain_database.rs b/base_layer/core/src/chain_storage/tests/blockchain_database.rs index 0d7365229d..2392717cb7 100644 --- a/base_layer/core/src/chain_storage/tests/blockchain_database.rs +++ b/base_layer/core/src/chain_storage/tests/blockchain_database.rs @@ -21,11 +21,11 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::{ - blocks::{Block, BlockHeader, NewBlockTemplate}, + blocks::{Block, BlockHeader, BlockHeaderAccumulatedData, ChainHeader, NewBlockTemplate}, chain_storage::{BlockchainDatabase, ChainStorageError}, consensus::ConsensusManager, crypto::tari_utilities::hex::Hex, - proof_of_work::Difficulty, + proof_of_work::{AchievedTargetDifficulty, Difficulty, PowAlgorithm}, tari_utilities::Hashable, test_helpers::{ blockchain::{create_new_blockchain, TempDatabase}, @@ -440,11 +440,14 @@ mod fetch_total_size_stats { use super::*; #[test] - fn it_works_when_db_is_empty() { + fn it_measures_the_number_of_entries() { let db = setup(); + let _ = add_many_chained_blocks(2, &db); let stats = db.fetch_total_size_stats().unwrap(); - // Returns one per db - assert_eq!(stats.sizes().len(), 20); + assert_eq!( + stats.sizes().iter().find(|s| s.name == "utxos_db").unwrap().num_entries, + 4003 + ); } } @@ -572,3 +575,52 @@ mod fetch_header_containing_kernel_mmr { matches!(err, ChainStorageError::ValueNotFound { .. }); } } + +mod clear_all_pending_headers { + use super::*; + + #[test] + fn it_clears_no_headers() { + let db = setup(); + assert_eq!(db.clear_all_pending_headers().unwrap(), 0); + let _ = add_many_chained_blocks(2, &db); + db.clear_all_pending_headers().unwrap(); + let last_header = db.fetch_last_header().unwrap(); + assert_eq!(last_header.height, 2); + } + + #[test] + fn it_clears_headers_after_tip() { + let db = setup(); + let _ = add_many_chained_blocks(2, &db); + let prev_block = db.fetch_block(2).unwrap(); + let mut prev_accum = prev_block.accumulated_data.clone(); + let mut prev_block = Arc::new(prev_block.try_into_block().unwrap()); + let headers = (0..5) + .map(|_| { + let (block, _) = create_next_block(&prev_block, vec![]); + let accum = BlockHeaderAccumulatedData::builder(&prev_accum) + .with_hash(block.hash()) + .with_achieved_target_difficulty( + AchievedTargetDifficulty::try_construct(PowAlgorithm::Sha3, 0.into(), 0.into()).unwrap(), + ) + .with_total_kernel_offset(Default::default()) + .build() + .unwrap(); + + let header = ChainHeader::try_construct(block.header.clone(), accum.clone()).unwrap(); + + prev_block = block; + prev_accum = accum; + header + }) + .collect(); + db.insert_valid_headers(headers).unwrap(); + let last_header = db.fetch_last_header().unwrap(); + assert_eq!(last_header.height, 7); + let num_deleted = db.clear_all_pending_headers().unwrap(); + assert_eq!(num_deleted, 5); + let last_header = db.fetch_last_header().unwrap(); + assert_eq!(last_header.height, 2); + } +} diff --git a/base_layer/core/src/test_helpers/blockchain.rs b/base_layer/core/src/test_helpers/blockchain.rs index f18b55ee65..39609fe0f7 100644 --- a/base_layer/core/src/test_helpers/blockchain.rs +++ b/base_layer/core/src/test_helpers/blockchain.rs @@ -314,6 +314,10 @@ impl BlockchainBackend for TempDatabase { self.db.as_ref().unwrap().fetch_last_header() } + fn clear_all_pending_headers(&self) -> Result { + self.db.as_ref().unwrap().clear_all_pending_headers() + } + fn fetch_last_chain_header(&self) -> Result { self.db.as_ref().unwrap().fetch_last_chain_header() } @@ -386,6 +390,10 @@ impl BlockchainBackend for TempDatabase { .unwrap() .fetch_header_hash_by_deleted_mmr_positions(mmr_positions) } + + fn bad_block_exists(&self, block_hash: HashOutput) -> Result { + self.db.as_ref().unwrap().bad_block_exists(block_hash) + } } pub fn create_chained_blocks>( diff --git a/base_layer/core/src/validation/block_validators/body_only.rs b/base_layer/core/src/validation/block_validators/body_only.rs index c6dfc9228f..2c9f133895 100644 --- a/base_layer/core/src/validation/block_validators/body_only.rs +++ b/base_layer/core/src/validation/block_validators/body_only.rs @@ -74,6 +74,7 @@ impl PostOrphanBodyValidation for BodyOnlyValidator { ); let mmr_roots = chain_storage::calculate_mmr_roots(backend, block.block())?; helpers::check_mmr_roots(block.header(), &mmr_roots)?; + helpers::check_not_bad_block(backend, block.hash())?; trace!( target: LOG_TARGET, "Block validation: MMR roots are valid for {}", diff --git a/base_layer/core/src/validation/error.rs b/base_layer/core/src/validation/error.rs index bc2f3bb0d5..52669c2223 100644 --- a/base_layer/core/src/validation/error.rs +++ b/base_layer/core/src/validation/error.rs @@ -89,6 +89,8 @@ pub enum ValidationError { IncorrectPreviousHash { expected: String, block_hash: String }, #[error("Async validation task failed: {0}")] AsyncTaskFailed(#[from] task::JoinError), + #[error("Bad block with hash {hash} found")] + BadBlockFound { hash: String }, } // ChainStorageError has a ValidationError variant, so to prevent a cyclic dependency we use a string representation in diff --git a/base_layer/core/src/validation/helpers.rs b/base_layer/core/src/validation/helpers.rs index 3a8f4b0152..41825f4532 100644 --- a/base_layer/core/src/validation/helpers.rs +++ b/base_layer/core/src/validation/helpers.rs @@ -20,15 +20,11 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::transactions::aggregated_body::AggregateBody; -use log::*; -use tari_crypto::tari_utilities::{epoch_time::EpochTime, hash::Hashable, hex::Hex}; - use crate::{ blocks::{Block, BlockHeader, BlockHeaderValidationError, BlockValidationError}, chain_storage::{BlockchainBackend, MmrRoots, MmrTree}, consensus::{emission::Emission, ConsensusConstants, ConsensusManager}, - crypto::commitment::HomomorphicCommitmentFactory, + crypto::{commitment::HomomorphicCommitmentFactory, tari_utilities::hex::to_hex}, proof_of_work::{ monero_difficulty, monero_rx::MoneroPowData, @@ -40,15 +36,20 @@ use crate::{ PowError, }, transactions::{ + aggregated_body::AggregateBody, tari_amount::MicroTari, transaction_entities::{KernelSum, TransactionError, TransactionInput, TransactionKernel, TransactionOutput}, CryptoFactories, }, validation::ValidationError, }; +use log::*; use std::cmp::Ordering; use tari_common_types::types::{Commitment, CommitmentFactory, PublicKey}; -use tari_crypto::keys::PublicKey as PublicKeyTrait; +use tari_crypto::{ + keys::PublicKey as PublicKeyTrait, + tari_utilities::{epoch_time::EpochTime, hash::Hashable, hex::Hex}, +}; pub const LOG_TARGET: &str = "c::val::helpers"; @@ -503,6 +504,13 @@ pub fn check_mmr_roots(header: &BlockHeader, mmr_roots: &MmrRoots) -> Result<(), Ok(()) } +pub fn check_not_bad_block(db: &B, hash: &[u8]) -> Result<(), ValidationError> { + if db.bad_block_exists(hash.to_vec())? { + return Err(ValidationError::BadBlockFound { hash: to_hex(hash) }); + } + Ok(()) +} + pub fn check_coinbase_reward( factory: &CommitmentFactory, rules: &ConsensusManager,