Skip to content

Commit

Permalink
refactor(base_layer): dan validation improvements (#4312)
Browse files Browse the repository at this point in the history
Description
---
* All helper functions for DAN validations are moved to `helpers.rs`, this cleans the code in each individual validator
* Improved some error handling:
    * Used the existing `MissingContractData` error variant when possible
    * Renaming `DuplicateUtxo` error type variant to `DuplicatedUtxo`
* Regarding unit testing:
    * Changed all error asserts to match the error variant instead of the error message
    * Used the existing `publish_contract` function when possible, to reduce boilerplate code
    * Refactored the available UTXOs to be an iterator instead of a vector. This allows us to avoid specifying each index when spending a UTXO (which is error prone as they can only be spent once).

Motivation and Context
---
During the development (and testing) of DAN base layer validations, some pain points were introduced. This PRs tries to clean some of them.

How Has This Been Tested?
---
All the existing unit tests for the DAN validations pass
  • Loading branch information
mrnaveira authored Jul 15, 2022
1 parent a2778f0 commit 3b467b9
Show file tree
Hide file tree
Showing 10 changed files with 480 additions and 412 deletions.
164 changes: 55 additions & 109 deletions base_layer/core/src/validation/dan_validators/acceptance_validator.rs

Large diffs are not rendered by default.

111 changes: 60 additions & 51 deletions base_layer/core/src/validation/dan_validators/amendment_validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,13 @@ use tari_common_types::types::FixedHash;
use super::helpers::{
fetch_contract_features,
fetch_contract_update_proposal,
get_contract_amendment,
get_sidechain_features,
validate_output_type,
};
use crate::{
chain_storage::{BlockchainBackend, BlockchainDatabase},
transactions::transaction_components::{
ContractAmendment,
ContractUpdateProposal,
OutputType,
SideChainFeatures,
TransactionOutput,
},
transactions::transaction_components::{ContractAmendment, ContractUpdateProposal, OutputType, TransactionOutput},
validation::{dan_validators::DanLayerValidationError, ValidationError},
};

Expand All @@ -59,17 +54,6 @@ pub fn validate_amendment<B: BlockchainBackend>(
Ok(())
}

fn get_contract_amendment(
sidechain_feature: &SideChainFeatures,
) -> Result<&ContractAmendment, DanLayerValidationError> {
match sidechain_feature.amendment.as_ref() {
Some(amendment) => Ok(amendment),
None => Err(DanLayerValidationError::SideChainFeaturesDataNotProvided {
field_name: "amendment",
}),
}
}

fn validate_uniqueness<B: BlockchainBackend>(
db: &BlockchainDatabase<B>,
contract_id: FixedHash,
Expand All @@ -81,11 +65,13 @@ fn validate_uniqueness<B: BlockchainBackend>(
.filter_map(|feature| feature.amendment)
.find(|amendment| amendment.proposal_id == proposal_id)
{
Some(_) => Err(ValidationError::DanLayerError(DanLayerValidationError::DuplicateUtxo {
contract_id,
output_type: OutputType::ContractAmendment,
details: format!("proposal_id = {}", proposal_id),
})),
Some(_) => Err(ValidationError::DanLayerError(
DanLayerValidationError::DuplicatedUtxo {
contract_id,
output_type: OutputType::ContractAmendment,
details: format!("proposal_id = {}", proposal_id),
},
)),
None => Ok(()),
}
}
Expand Down Expand Up @@ -113,7 +99,6 @@ mod test {
validation::dan_validators::{
test_helpers::{
assert_dan_validator_err,
assert_dan_validator_fail,
assert_dan_validator_success,
create_block,
create_contract_amendment_schema,
Expand All @@ -131,100 +116,119 @@ mod test {
#[test]
fn it_allows_valid_amendments() {
// initialise a blockchain with enough funds to spend at contract transactions
let (mut blockchain, change) = init_test_blockchain();
let (mut blockchain, mut utxos) = init_test_blockchain();

// publish the contract definition into a block
let contract_id = publish_definition(&mut blockchain, change[0].clone());
let contract_id = publish_definition(&mut blockchain, utxos.next().unwrap());

// publish the contract constitution into a block
let validator_node_public_key = PublicKey::default();
let committee = vec![validator_node_public_key];
let mut constitution = create_contract_constitution();
constitution.validator_committee = committee.try_into().unwrap();
publish_constitution(&mut blockchain, change[1].clone(), contract_id, constitution.clone());
publish_constitution(
&mut blockchain,
utxos.next().unwrap(),
contract_id,
constitution.clone(),
);

// publish a contract update proposal into a block
let proposal_id: u64 = 1;
publish_update_proposal(
&mut blockchain,
change[2].clone(),
utxos.next().unwrap(),
contract_id,
proposal_id,
constitution.clone(),
);

// create a valid amendment transaction
let proposal_id = 1;
let schema = create_contract_amendment_schema(contract_id, change[3].clone(), proposal_id, constitution);
let schema = create_contract_amendment_schema(contract_id, utxos.next().unwrap(), proposal_id, constitution);
let (tx, _) = schema_to_transaction(&schema);
assert_dan_validator_success(&blockchain, &tx);
}

#[test]
fn proposal_must_exist() {
// initialise a blockchain with enough funds to spend at contract transactions
let (mut blockchain, change) = init_test_blockchain();
let (mut blockchain, mut utxos) = init_test_blockchain();

// publish the contract definition into a block
let contract_id = publish_definition(&mut blockchain, change[0].clone());
let contract_id = publish_definition(&mut blockchain, utxos.next().unwrap());

// publish the contract constitution into a block
let validator_node_public_key = PublicKey::default();
let committee = vec![validator_node_public_key];
let mut constitution = create_contract_constitution();
constitution.validator_committee = committee.try_into().unwrap();
publish_constitution(&mut blockchain, change[1].clone(), contract_id, constitution.clone());
publish_constitution(
&mut blockchain,
utxos.next().unwrap(),
contract_id,
constitution.clone(),
);

// skip the publication of the contract update proposal

// create an amendment transaction
let proposal_id = 1;
let schema = create_contract_amendment_schema(contract_id, change[1].clone(), proposal_id, constitution);
let schema = create_contract_amendment_schema(contract_id, utxos.next().unwrap(), proposal_id, constitution);
let (tx, _) = schema_to_transaction(&schema);

// try to validate the acceptance transaction and check that we get the error
assert_dan_validator_fail(&blockchain, &tx, "Contract update proposal not found");
let err = assert_dan_validator_err(&blockchain, &tx);
assert!(matches!(
err,
DanLayerValidationError::ContractUpdateProposalNotFound { .. }
))
}

#[test]
fn it_rejects_duplicated_amendments() {
// initialise a blockchain with enough funds to spend at contract transactions
let (mut blockchain, change) = init_test_blockchain();
let (mut blockchain, mut utxos) = init_test_blockchain();

// publish the contract definition into a block
let contract_id = publish_definition(&mut blockchain, change[0].clone());
let contract_id = publish_definition(&mut blockchain, utxos.next().unwrap());

// publish the contract constitution into a block
let validator_node_public_key = PublicKey::default();
let committee = vec![validator_node_public_key];
let mut constitution = create_contract_constitution();
constitution.validator_committee = committee.try_into().unwrap();
publish_constitution(&mut blockchain, change[1].clone(), contract_id, constitution.clone());
publish_constitution(
&mut blockchain,
utxos.next().unwrap(),
contract_id,
constitution.clone(),
);

// publish a contract update proposal into a block
let proposal_id: u64 = 1;
publish_update_proposal(
&mut blockchain,
change[2].clone(),
utxos.next().unwrap(),
contract_id,
proposal_id,
constitution.clone(),
);

// publish the contract amendment into a block
let schema =
create_contract_amendment_schema(contract_id, change[3].clone(), proposal_id, constitution.clone());
create_contract_amendment_schema(contract_id, utxos.next().unwrap(), proposal_id, constitution.clone());
create_block(&mut blockchain, "amendment", schema);

// create a (duplicated) contract amendment transaction
let schema = create_contract_amendment_schema(contract_id, change[4].clone(), proposal_id, constitution);
let schema = create_contract_amendment_schema(contract_id, utxos.next().unwrap(), proposal_id, constitution);
let (tx, _) = schema_to_transaction(&schema);

// try to validate the duplicated amendment transaction and check that we get the error
let err = assert_dan_validator_err(&blockchain, &tx);
let expected_contract_id = contract_id;
unpack_enum!(
DanLayerValidationError::DuplicateUtxo {
DanLayerValidationError::DuplicatedUtxo {
output_type,
contract_id,
..
Expand All @@ -237,23 +241,28 @@ mod test {
#[test]
fn it_rejects_altered_updated_constitution() {
// initialise a blockchain with enough funds to spend at contract transactions
let (mut blockchain, change) = init_test_blockchain();
let (mut blockchain, mut utxos) = init_test_blockchain();

// publish the contract definition into a block
let contract_id = publish_definition(&mut blockchain, change[0].clone());
let contract_id = publish_definition(&mut blockchain, utxos.next().unwrap());

// publish the contract constitution into a block
let validator_node_public_key = PublicKey::default();
let committee = vec![validator_node_public_key];
let mut constitution = create_contract_constitution();
constitution.validator_committee = committee.try_into().unwrap();
publish_constitution(&mut blockchain, change[1].clone(), contract_id, constitution.clone());
publish_constitution(
&mut blockchain,
utxos.next().unwrap(),
contract_id,
constitution.clone(),
);

// publish a contract update proposal into a block
let proposal_id: u64 = 1;
publish_update_proposal(
&mut blockchain,
change[2].clone(),
utxos.next().unwrap(),
contract_id,
proposal_id,
constitution,
Expand All @@ -263,14 +272,14 @@ mod test {
let mut altered_constitution = create_contract_constitution();
altered_constitution.validator_committee = vec![].try_into().unwrap();
let schema =
create_contract_amendment_schema(contract_id, change[4].clone(), proposal_id, altered_constitution);
create_contract_amendment_schema(contract_id, utxos.next().unwrap(), proposal_id, altered_constitution);
let (tx, _) = schema_to_transaction(&schema);

// try to validate the amendment transaction and check that we get the error
assert_dan_validator_fail(
&blockchain,
&tx,
"The updated_constitution of the amendment does not match the one in the update proposal",
);
let err = assert_dan_validator_err(&blockchain, &tx);
assert!(matches!(
err,
DanLayerValidationError::UpdatedConstitutionAmendmentMismatch { .. }
))
}
}
Loading

0 comments on commit 3b467b9

Please sign in to comment.