Skip to content

Commit

Permalink
fix(consensus): check blockchain version within valid range (#3916)
Browse files Browse the repository at this point in the history
Description
---

- adds consensus constant for valid block version range
- adds check in header validator for valid version range 

Motivation and Context
---
Currently the block version is not checked as so may contain an arbitrary version. This PR fixes that by adding this check to the header validators.

How Has This Been Tested?
---
Added a unit test
  • Loading branch information
sdbondi authored Mar 15, 2022
1 parent 36a71a7 commit faf23f3
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 9 deletions.
5 changes: 4 additions & 1 deletion base_layer/core/src/base_node/sync/header_sync/validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -116,6 +117,9 @@ impl<B: BlockchainBackend + 'static> BlockHeaderSyncValidator<B> {

pub fn validate(&mut self, header: BlockHeader) -> Result<u128, BlockHeaderSyncError> {
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 {
Expand All @@ -133,7 +137,6 @@ impl<B: BlockchainBackend + 'static> BlockHeaderSyncValidator<B> {

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()),
Expand Down
12 changes: 12 additions & 0 deletions base_layer/core/src/consensus/consensus_constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u16>,
/// 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,
Expand Down Expand Up @@ -147,6 +149,11 @@ impl ConsensusConstants {
self.blockchain_version
}

/// Returns the valid blockchain version range
pub fn valid_blockchain_version_range(&self) -> &RangeInclusive<u16> {
&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 {
Expand Down Expand Up @@ -284,6 +291,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,
Expand Down Expand Up @@ -322,6 +330,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,
Expand Down Expand Up @@ -360,6 +369,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
Expand Down Expand Up @@ -407,6 +417,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
Expand Down Expand Up @@ -451,6 +462,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,
Expand Down
2 changes: 2 additions & 0 deletions base_layer/core/src/validation/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 10 additions & 7 deletions base_layer/core/src/validation/header_validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -34,18 +35,17 @@ impl HeaderValidator {
fn check_median_timestamp<B: BlockchainBackend>(
&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(
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)
Expand All @@ -69,14 +69,17 @@ impl<TBackend: BlockchainBackend> HeaderValidation<TBackend> for HeaderValidator
header: &BlockHeader,
difficulty_calculator: &DifficultyCalculator,
) -> Result<AchievedTargetDifficulty, ValidationError> {
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!(
target: LOG_TARGET,
"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 {} ",
Expand Down
8 changes: 8 additions & 0 deletions base_layer/core/src/validation/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down
29 changes: 28 additions & 1 deletion base_layer/core/src/validation/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,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() {
Expand Down Expand Up @@ -94,6 +100,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]
Expand Down

0 comments on commit faf23f3

Please sign in to comment.