Skip to content

Commit

Permalink
Refactor the validators to ensure that each validation step only comp…
Browse files Browse the repository at this point in the history
…letes once.
  • Loading branch information
SWvheerden committed May 15, 2020
1 parent a713a30 commit 8e2b5e6
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 26 deletions.
2 changes: 1 addition & 1 deletion applications/tari_base_node/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ where
let factories = CryptoFactories::default();
let validators = Validators::new(
FullConsensusValidator::new(rules.clone(), factories.clone()),
StatelessBlockValidator::new(&rules.consensus_constants()),
StatelessBlockValidator::new(rules.clone(), factories.clone()),
AccumDifficultyValidator {},
);
let db_config = BlockchainDatabaseConfig {
Expand Down
67 changes: 49 additions & 18 deletions base_layer/core/src/validation/block_validators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,27 +45,29 @@ pub const LOG_TARGET: &str = "c::val::block_validators";
/// This validator tests whether a candidate block is internally consistent
#[derive(Clone)]
pub struct StatelessBlockValidator {
consensus_constants: ConsensusConstants,
rules: ConsensusManager,
factories: CryptoFactories,
}

impl StatelessBlockValidator {
pub fn new(consensus_constants: &ConsensusConstants) -> Self {
Self {
consensus_constants: consensus_constants.clone(),
}
pub fn new(rules: ConsensusManager, factories: CryptoFactories) -> Self {
Self { rules, factories }
}
}

impl StatelessValidation<Block> for StatelessBlockValidator {
/// The consensus checks that are done (in order of cheapest to verify to most expensive):
/// 1. Is there precisely one Coinbase output and is it correctly defined?
/// 1. Is the block weight of the block under the prescribed limit?
/// 1. Does it contain only unique inputs and outputs?
/// 1. Where all the rules for the spent outputs followed?
/// 1. Was cut through applied in the block?
/// 1. Is the accounting correct?
/// 1. Are all inputs allowed to be spent (Are the feature flags satisfied)
fn validate(&self, block: &Block) -> Result<(), ValidationError> {
let block_id = format!("block #{} ({})", block.header.height, block.hash().to_hex());
check_coinbase_output(block, &self.consensus_constants)?;
check_coinbase_output(block, &self.rules.consensus_constants())?;
trace!(target: LOG_TARGET, "SV - Coinbase output is ok for {} ", &block_id);
check_block_weight(block, &self.consensus_constants)?;
check_block_weight(block, &self.rules.consensus_constants())?;
trace!(target: LOG_TARGET, "SV - Block weight is ok for {} ", &block_id);
// Check that the inputs are are allowed to be spent
block.check_stxo_rules().map_err(BlockValidationError::from)?;
Expand All @@ -76,6 +78,9 @@ impl StatelessValidation<Block> for StatelessBlockValidator {
target: LOG_TARGET,
"{} has PASSED stateless VALIDATION check.", &block_id
);

check_accounting_balance(block, self.rules.clone(), &self.factories)?;
trace!(target: LOG_TARGET, "SV - accounting balance correct for {}", &block_id);
Ok(())
}
}
Expand Down Expand Up @@ -104,16 +109,6 @@ impl<B: BlockchainBackend> Validation<Block, B> for FullConsensusValidator {
/// 1. Is the achieved difficulty of this block >= the target difficulty for this block?
fn validate(&self, block: &Block, db: &B) -> Result<(), ValidationError> {
let block_id = format!("block #{} ({})", block.header.height, block.hash().to_hex());
check_coinbase_output(block, &self.rules.consensus_constants())?;
trace!(target: LOG_TARGET, "FCV - Coinbase output ok for {}", &block_id);
check_block_weight(block, &self.rules.consensus_constants())?;
trace!(target: LOG_TARGET, "FCV - Block weight ok for {}", &block_id);
check_cut_through(block)?;
trace!(target: LOG_TARGET, "FCV - Cut-though correct for {}", &block_id);
block.check_stxo_rules().map_err(BlockValidationError::from)?;
trace!(target: LOG_TARGET, "FCV - Cut-though correct for {}", &block_id);
check_accounting_balance(block, self.rules.clone(), &self.factories)?;
trace!(target: LOG_TARGET, "FCV - Cut-though correct for {}", &block_id);
check_inputs_are_utxos(block, db)?;
trace!(target: LOG_TARGET, "FCV - All inputs are valid for {}", &block_id);
check_mmr_roots(block, db)?;
Expand All @@ -134,13 +129,49 @@ impl<B: BlockchainBackend> Validation<Block, B> for FullConsensusValidator {
}
}

/// This validator tests whether a candidate block is internally consistent, BUT it does not check internal accounting
/// as some tests use odd values.
#[derive(Clone)]
pub struct MockStatelessBlockValidator {
rules: ConsensusManager,
factories: CryptoFactories,
}

impl MockStatelessBlockValidator {
pub fn new(rules: ConsensusManager, factories: CryptoFactories) -> Self {
Self { rules, factories }
}
}

impl StatelessValidation<Block> for MockStatelessBlockValidator {
/// The consensus checks that are done (in order of cheapest to verify to most expensive):
/// 1. Is there precisely one Coinbase output and is it correctly defined?
/// 1. Is the block weight of the block under the prescribed limit?
/// 1. Does it contain only unique inputs and outputs?
/// 1. Where all the rules for the spent outputs followed?
/// 1. Was cut through applied in the block?
fn validate(&self, block: &Block) -> Result<(), ValidationError> {
check_coinbase_output(block, &self.rules.consensus_constants())?;
check_block_weight(block, &self.rules.consensus_constants())?;
// Check that the inputs are are allowed to be spent
block.check_stxo_rules().map_err(BlockValidationError::from)?;
check_cut_through(block)?;

Ok(())
}
}

//------------------------------------- Block validator helper functions -------------------------------------//
fn check_accounting_balance(
block: &Block,
rules: ConsensusManager,
factories: &CryptoFactories,
) -> Result<(), ValidationError>
{
if block.header.height == 0 {
// Gen block does not need to be checked for this.
return Ok(());
}
let offset = &block.header.total_kernel_offset;
let total_coinbase = rules.calculate_coinbase_and_fees(block);
block
Expand Down
4 changes: 2 additions & 2 deletions base_layer/core/tests/block_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ fn test_genesis_block() {
let rules = ConsensusManagerBuilder::new(network).build();
let backend = MemoryDatabase::<HashDigest>::default();
let validators = Validators::new(
FullConsensusValidator::new(rules.clone(), factories),
StatelessBlockValidator::new(&rules.consensus_constants()),
FullConsensusValidator::new(rules.clone(), factories.clone()),
StatelessBlockValidator::new(rules.clone(), factories),
AccumDifficultyValidator {},
);
let db = BlockchainDatabase::new(backend, &rules, validators, BlockchainDatabaseConfig::default()).unwrap();
Expand Down
4 changes: 2 additions & 2 deletions base_layer/core/tests/chain_storage_tests/chain_storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ use tari_core::{
txn_schema,
validation::{
accum_difficulty_validators::MockAccumDifficultyValidator,
block_validators::StatelessBlockValidator,
block_validators::{MockStatelessBlockValidator, StatelessBlockValidator},
mocks::MockValidator,
},
};
Expand Down Expand Up @@ -873,7 +873,7 @@ fn invalid_block() {
.build();
let validators = Validators::new(
MockValidator::new(true),
StatelessBlockValidator::new(&consensus_manager.consensus_constants()),
MockStatelessBlockValidator::new(consensus_manager.clone(), factories.clone()),
MockAccumDifficultyValidator {},
);
let db = create_lmdb_database(&temp_path, MmrCacheConfig::default()).unwrap();
Expand Down
2 changes: 1 addition & 1 deletion base_layer/core/tests/node_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ fn propagate_and_forward_invalid_block() {
.with_consensus_constants(consensus_constants)
.with_block(block0.clone())
.build();
let stateless_block_validator = StatelessBlockValidator::new(&rules.consensus_constants());
let stateless_block_validator = StatelessBlockValidator::new(rules.clone(), factories.clone());
let mock_validator = MockValidator::new(true);
let mock_accum_difficulty_validator = MockAccumDifficultyValidator {};
let (mut alice_node, rules) = BaseNodeBuilder::new(network)
Expand Down
4 changes: 2 additions & 2 deletions base_layer/core/tests/node_state_machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ use tari_core::{
transactions::types::CryptoFactories,
validation::{
accum_difficulty_validators::MockAccumDifficultyValidator,
block_validators::StatelessBlockValidator,
block_validators::{MockStatelessBlockValidator, StatelessBlockValidator},
mocks::MockValidator,
},
};
Expand Down Expand Up @@ -605,7 +605,7 @@ fn test_sync_peer_banning() {
.with_consensus_constants(consensus_constants)
.with_block(prev_block.clone())
.build();
let stateless_block_validator = StatelessBlockValidator::new(consensus_manager.consensus_constants());
let stateless_block_validator = MockStatelessBlockValidator::new(consensus_manager.clone(), factories.clone());
let mock_validator = MockValidator::new(true);
// Create base nodes
let alice_node_identity = random_node_identity();
Expand Down

0 comments on commit 8e2b5e6

Please sign in to comment.