From 227beec2a704fca388238dfa7784c9c24a3aaff6 Mon Sep 17 00:00:00 2001 From: Stanimal Date: Mon, 14 Mar 2022 14:03:01 +0400 Subject: [PATCH] fix(consensus): add valid range for blockchain version --- .../base_node/sync/header_sync/validator.rs | 5 +++- .../core/src/consensus/consensus_constants.rs | 12 ++++++++ base_layer/core/src/validation/error.rs | 2 ++ .../core/src/validation/header_validator.rs | 17 ++++++----- base_layer/core/src/validation/helpers.rs | 8 +++++ base_layer/core/src/validation/test.rs | 29 ++++++++++++++++++- 6 files changed, 64 insertions(+), 9 deletions(-) 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 cd455af1362..226ef586e69 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 @@ -33,6 +33,7 @@ use crate::{ consensus::ConsensusManager, proof_of_work::{randomx_factory::RandomXFactory, PowAlgorithm}, validation::helpers::{ + check_blockchain_version, check_header_timestamp_greater_than_median, check_not_bad_block, check_pow_data, @@ -116,6 +117,9 @@ impl BlockHeaderSyncValidator { pub fn validate(&mut self, header: BlockHeader) -> Result { let state = self.state(); + let constants = self.consensus_rules.consensus_constants(header.height); + check_blockchain_version(&constants, header.version)?; + let expected_height = state.current_height + 1; if header.height != expected_height { return Err(BlockHeaderSyncError::InvalidBlockHeight { @@ -133,7 +137,6 @@ impl BlockHeaderSyncValidator { check_header_timestamp_greater_than_median(&header, &state.timestamps)?; - let constants = self.consensus_rules.consensus_constants(header.height); let target_difficulty = state.target_difficulties.get(header.pow_algo()).calculate( constants.min_pow_difficulty(header.pow_algo()), constants.max_pow_difficulty(header.pow_algo()), diff --git a/base_layer/core/src/consensus/consensus_constants.rs b/base_layer/core/src/consensus/consensus_constants.rs index 4723f241341..580cf604d6f 100644 --- a/base_layer/core/src/consensus/consensus_constants.rs +++ b/base_layer/core/src/consensus/consensus_constants.rs @@ -54,6 +54,8 @@ pub struct ConsensusConstants { coinbase_lock_height: u64, /// Current version of the blockchain blockchain_version: u16, + /// Current version of the blockchain + valid_blockchain_version_range: RangeInclusive, /// The Future Time Limit (FTL) of the blockchain in seconds. This is the max allowable timestamp that is excepted. /// We use T*N/20 where T = desired chain target time, and N = block_window future_time_limit: u64, @@ -147,6 +149,11 @@ impl ConsensusConstants { self.blockchain_version } + /// Returns the valid blockchain version range + pub fn valid_blockchain_version_range(&self) -> &RangeInclusive { + &self.valid_blockchain_version_range + } + /// This returns the FTL (Future Time Limit) for blocks. /// Any block with a timestamp greater than this is rejected. pub fn ftl(&self) -> EpochTime { @@ -283,6 +290,7 @@ impl ConsensusConstants { effective_from_height: 0, coinbase_lock_height: 2, blockchain_version: 1, + valid_blockchain_version_range: 0..=3, future_time_limit: 540, difficulty_block_window, max_block_transaction_weight: 19500, @@ -321,6 +329,7 @@ impl ConsensusConstants { effective_from_height: 0, coinbase_lock_height: 6, blockchain_version: 1, + valid_blockchain_version_range: 0..=3, future_time_limit: 540, difficulty_block_window: 90, max_block_transaction_weight: 19500, @@ -359,6 +368,7 @@ impl ConsensusConstants { effective_from_height: 0, coinbase_lock_height: 6, blockchain_version: 2, + valid_blockchain_version_range: 0..=3, future_time_limit: 540, difficulty_block_window: 90, // 65536 = target_block_size / bytes_per_gram = (1024*1024) / 16 @@ -406,6 +416,7 @@ impl ConsensusConstants { effective_from_height: 0, coinbase_lock_height: 360, blockchain_version: 2, + valid_blockchain_version_range: 0..=3, future_time_limit: 540, difficulty_block_window: 90, // 65536 = target_block_size / bytes_per_gram = (1024*1024) / 16 @@ -450,6 +461,7 @@ impl ConsensusConstants { effective_from_height: 0, coinbase_lock_height: 1, blockchain_version: 1, + valid_blockchain_version_range: 0..=0, future_time_limit: 540, difficulty_block_window, max_block_transaction_weight: 19500, diff --git a/base_layer/core/src/validation/error.rs b/base_layer/core/src/validation/error.rs index 44c7bdb1a53..f253a8ee6b8 100644 --- a/base_layer/core/src/validation/error.rs +++ b/base_layer/core/src/validation/error.rs @@ -112,6 +112,8 @@ pub enum ValidationError { ConsensusError(String), #[error("Covenant failed to validate: {0}")] CovenantError(#[from] CovenantError), + #[error("Invalid or unsupported blockchain version {version}")] + InvalidBlockchainVersion { version: u16 }, } // 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/header_validator.rs b/base_layer/core/src/validation/header_validator.rs index 4351ed69ce0..69d0ec665c1 100644 --- a/base_layer/core/src/validation/header_validator.rs +++ b/base_layer/core/src/validation/header_validator.rs @@ -4,10 +4,11 @@ use tari_crypto::tari_utilities::{hash::Hashable, hex::Hex}; use crate::{ blocks::BlockHeader, chain_storage::{fetch_headers, BlockchainBackend}, - consensus::ConsensusManager, + consensus::{ConsensusConstants, ConsensusManager}, proof_of_work::AchievedTargetDifficulty, validation::{ helpers::{ + check_blockchain_version, check_header_timestamp_greater_than_median, check_not_bad_block, check_pow_data, @@ -34,6 +35,7 @@ impl HeaderValidator { fn check_median_timestamp( &self, db: &B, + constants: &ConsensusConstants, block_header: &BlockHeader, ) -> Result<(), ValidationError> { if block_header.height == 0 { @@ -41,11 +43,9 @@ impl HeaderValidator { } let height = block_header.height - 1; - let min_height = block_header.height.saturating_sub( - self.rules - .consensus_constants(block_header.height) - .get_median_timestamp_count() as u64, - ); + 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) @@ -69,6 +69,9 @@ impl HeaderValidation for HeaderValidator header: &BlockHeader, difficulty_calculator: &DifficultyCalculator, ) -> Result { + let constants = self.rules.consensus_constants(header.height); + check_blockchain_version(constants, header.version)?; + check_timestamp_ftl(header, &self.rules)?; let header_id = format!("header #{} ({})", header.height, header.hash().to_hex()); trace!( @@ -76,7 +79,7 @@ impl HeaderValidation for HeaderValidator "BlockHeader validation: FTL timestamp is ok for {} ", header_id ); - self.check_median_timestamp(backend, header)?; + self.check_median_timestamp(backend, constants, header)?; trace!( target: LOG_TARGET, "BlockHeader validation: Median timestamp is ok for {} ", diff --git a/base_layer/core/src/validation/helpers.rs b/base_layer/core/src/validation/helpers.rs index fbefea2499a..ef9c5601279 100644 --- a/base_layer/core/src/validation/helpers.rs +++ b/base_layer/core/src/validation/helpers.rs @@ -764,6 +764,14 @@ pub fn check_maturity(height: u64, inputs: &[TransactionInput]) -> Result<(), Tr Ok(()) } +pub fn check_blockchain_version(constants: &ConsensusConstants, version: u16) -> Result<(), ValidationError> { + if constants.valid_blockchain_version_range().contains(&version) { + Ok(()) + } else { + Err(ValidationError::InvalidBlockchainVersion { version }) + } +} + #[cfg(test)] mod test { use super::*; diff --git a/base_layer/core/src/validation/test.rs b/base_layer/core/src/validation/test.rs index fb46d855e49..62afe5d6bf2 100644 --- a/base_layer/core/src/validation/test.rs +++ b/base_layer/core/src/validation/test.rs @@ -40,11 +40,17 @@ use crate::{ transaction_components::{KernelBuilder, KernelFeatures, OutputFeatures, TransactionKernel}, CryptoFactories, }, - validation::{header_iter::HeaderIter, ChainBalanceValidator, FinalHorizonStateValidation}, + validation::{ + header_iter::HeaderIter, + header_validator::HeaderValidator, + ChainBalanceValidator, + FinalHorizonStateValidation, + }, }; mod header_validators { use super::*; + use crate::validation::{DifficultyCalculator, HeaderValidation, ValidationError}; #[test] fn header_iter_empty_and_invalid_height() { @@ -93,6 +99,27 @@ mod header_validators { assert_eq!(headers[i].height, i as u64); }) } + + #[test] + fn it_validates_that_version_is_in_range() { + let consensus_manager = ConsensusManagerBuilder::new(Network::LocalNet).build(); + let db = create_store_with_consensus(consensus_manager.clone()); + + let genesis = db.fetch_chain_header(0).unwrap(); + + let mut header = BlockHeader::from_previous(genesis.header()); + header.version = u16::MAX; + + let validator = HeaderValidator::new(consensus_manager.clone()); + + let difficulty_calculator = DifficultyCalculator::new(consensus_manager, Default::default()); + let err = validator + .validate(&*db.db_read_access().unwrap(), &header, &difficulty_calculator) + .unwrap_err(); + assert!(matches!(err, ValidationError::InvalidBlockchainVersion { + version: u16::MAX + })); + } } #[test]