diff --git a/base_layer/core/src/validation/block_validators/async_validator.rs b/base_layer/core/src/validation/block_validators/async_validator.rs index 6058d59efd..af446e357e 100644 --- a/base_layer/core/src/validation/block_validators/async_validator.rs +++ b/base_layer/core/src/validation/block_validators/async_validator.rs @@ -34,7 +34,7 @@ use super::LOG_TARGET; use crate::{ blocks::{Block, BlockHeader}, chain_storage::{async_db::AsyncBlockchainDb, BlockchainBackend, PrunedOutput}, - consensus::ConsensusManager, + consensus::{ConsensusConstants, ConsensusManager}, iterators::NonOverlappingIntegerPairIter, transactions::{ aggregated_body::AggregateBody, @@ -50,7 +50,7 @@ use crate::{ }, validation::{ block_validators::abort_on_drop::AbortOnDropJoinHandle, - helpers, + helpers::{self, check_header_timestamp_greater_than_median}, BlockSyncBodyValidation, ValidationError, }, @@ -85,6 +85,32 @@ impl BlockValidator { } } + pub async fn check_median_timestamp( + &self, + constants: &ConsensusConstants, + block_header: &BlockHeader, + ) -> Result<(), ValidationError> { + if block_header.height == 0 { + return Ok(()); // Its the genesis block, so we dont have to check median + } + + let height = block_header.height - 1; + let min_height = block_header + .height + .saturating_sub(constants.get_median_timestamp_count() as u64); + let timestamps = self + .db + .fetch_headers(min_height..=height) + .await? + .iter() + .map(|h| h.timestamp) + .collect::>(); + + check_header_timestamp_greater_than_median(block_header, ×tamps)?; + + Ok(()) + } + async fn check_mmr_roots(&self, block: Block) -> Result { let (block, mmr_roots) = self.db.calculate_mmr_roots(block).await?; helpers::check_mmr_roots(&block.header, &mmr_roots)?; @@ -489,6 +515,7 @@ impl BlockSyncBodyValidation for BlockValidator< ); let constants = self.rules.consensus_constants(block.header.height); + self.check_median_timestamp(constants, &block.header).await?; helpers::check_block_weight(&block, constants)?; trace!(target: LOG_TARGET, "SV - Block weight is ok for {} ", &block_id); let block = self.validate_block_body(block).await?; 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 0b2fbf0fd0..bfee04b11b 100644 --- a/base_layer/core/src/validation/block_validators/body_only.rs +++ b/base_layer/core/src/validation/block_validators/body_only.rs @@ -26,11 +26,15 @@ use tari_utilities::hex::Hex; use super::LOG_TARGET; use crate::{ - blocks::ChainBlock, + blocks::{BlockHeader, ChainBlock}, chain_storage, - chain_storage::BlockchainBackend, - consensus::ConsensusManager, - validation::{helpers, PostOrphanBodyValidation, ValidationError}, + chain_storage::{fetch_headers, BlockchainBackend}, + consensus::{ConsensusConstants, ConsensusManager}, + validation::{ + helpers::{self, check_header_timestamp_greater_than_median}, + PostOrphanBodyValidation, + ValidationError, + }, }; /// This validator tests whether a candidate block is internally consistent. @@ -46,6 +50,31 @@ impl BodyOnlyValidator { pub fn new(rules: ConsensusManager) -> Self { Self { rules } } + + /// This function tests that the block timestamp is greater than the median timestamp at the specified height. + fn check_median_timestamp( + &self, + db: &B, + constants: &ConsensusConstants, + block_header: &BlockHeader, + ) -> Result<(), ValidationError> { + if block_header.height == 0 { + return Ok(()); // Its the genesis block, so we dont have to check median + } + + let height = block_header.height - 1; + let min_height = block_header + .height + .saturating_sub(constants.get_median_timestamp_count() as u64); + let timestamps = fetch_headers(db, min_height, height)? + .iter() + .map(|h| h.timestamp) + .collect::>(); + + check_header_timestamp_greater_than_median(block_header, ×tamps)?; + + Ok(()) + } } impl PostOrphanBodyValidation for BodyOnlyValidator { @@ -61,6 +90,8 @@ impl PostOrphanBodyValidation for BodyOnlyValidator { block: &ChainBlock, metadata: &ChainMetadata, ) -> Result<(), ValidationError> { + let constants = self.rules.consensus_constants(block.header().height); + if block.header().prev_hash != *metadata.best_block() { return Err(ValidationError::IncorrectPreviousHash { expected: metadata.best_block().to_hex(), @@ -74,6 +105,8 @@ impl PostOrphanBodyValidation for BodyOnlyValidator { }); } + self.check_median_timestamp(backend, constants, block.header())?; + let block_id = format!("block #{} ({})", block.header().height, block.hash().to_hex()); helpers::check_inputs_are_utxos(backend, &block.block().body)?; helpers::check_outputs( diff --git a/base_layer/core/src/validation/header_validator.rs b/base_layer/core/src/validation/header_validator.rs index 97858bcd04..186802e5b6 100644 --- a/base_layer/core/src/validation/header_validator.rs +++ b/base_layer/core/src/validation/header_validator.rs @@ -6,17 +6,11 @@ use tari_utilities::hex::Hex; use crate::{ blocks::BlockHeader, - chain_storage::{fetch_headers, BlockchainBackend}, - consensus::{ConsensusConstants, ConsensusManager}, + chain_storage::BlockchainBackend, + consensus::ConsensusManager, proof_of_work::AchievedTargetDifficulty, validation::{ - helpers::{ - check_blockchain_version, - check_header_timestamp_greater_than_median, - check_not_bad_block, - check_pow_data, - check_timestamp_ftl, - }, + helpers::{check_blockchain_version, check_not_bad_block, check_pow_data, check_timestamp_ftl}, DifficultyCalculator, HeaderValidation, ValidationError, @@ -33,31 +27,6 @@ impl HeaderValidator { pub fn new(rules: ConsensusManager) -> Self { Self { rules } } - - /// This function tests that the block timestamp is greater than the median timestamp at the specified height. - fn check_median_timestamp( - &self, - db: &B, - constants: &ConsensusConstants, - block_header: &BlockHeader, - ) -> Result<(), ValidationError> { - if block_header.height == 0 { - return Ok(()); // Its the genesis block, so we dont have to check median - } - - let height = block_header.height - 1; - let min_height = block_header - .height - .saturating_sub(constants.get_median_timestamp_count() as u64); - let timestamps = fetch_headers(db, min_height, height)? - .iter() - .map(|h| h.timestamp) - .collect::>(); - - check_header_timestamp_greater_than_median(block_header, ×tamps)?; - - Ok(()) - } } impl HeaderValidation for HeaderValidator { @@ -82,12 +51,6 @@ impl HeaderValidation for HeaderValidator "BlockHeader validation: FTL timestamp is ok for {} ", header_id ); - self.check_median_timestamp(backend, constants, header)?; - trace!( - target: LOG_TARGET, - "BlockHeader validation: Median timestamp is ok for {} ", - header_id - ); check_pow_data(header, &self.rules, backend)?; let achieved_target = difficulty_calculator.check_achieved_and_target_difficulty(backend, header)?; check_not_bad_block(backend, header.hash())?; diff --git a/base_layer/core/tests/block_validation.rs b/base_layer/core/tests/block_validation.rs index 01037db622..ba52cb435e 100644 --- a/base_layer/core/tests/block_validation.rs +++ b/base_layer/core/tests/block_validation.rs @@ -612,20 +612,6 @@ OutputFeatures::default()), ) .is_err()); - // lets break the median rules - let mut new_block = db.prepare_new_block(template.clone()).unwrap(); - new_block.header.nonce = OsRng.next_u64(); - // we take the max ftl time and give 10 seconds for mining then check it, it should still be more than the ftl - new_block.header.timestamp = genesis.header().timestamp.checked_sub(100.into()).unwrap(); - find_header_with_achieved_difficulty(&mut new_block.header, 20.into()); - assert!(header_validator - .validate( - &*db.db_read_access().unwrap(), - &new_block.header, - &difficulty_calculator, - ) - .is_err()); - // lets break difficulty let mut new_block = db.prepare_new_block(template).unwrap(); new_block.header.nonce = OsRng.next_u64();