Skip to content

Commit

Permalink
feat(base_layer): basic contract constitution validation (#4232)
Browse files Browse the repository at this point in the history
Description
---
* New DAN base layer validator for contract constitutions, checks that:
    * The definition for the contract must be published beforehand
    * There is no other constitution already published for that contract
* Refactored some redundant helper code for validations

Motivation and Context
---
The base layer should check the basic requirements of a valid contract constitution: the definition must already be published and there are no duplicated constitutions.

How Has This Been Tested?
---
* New unit tests to check the new validations
* The existing unit and integration tests pass
  • Loading branch information
mrnaveira authored Jun 27, 2022
1 parent 0a2812c commit c2efd5e
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

use tari_common_types::types::PublicKey;

use super::helpers::{get_contract_constitution, get_sidechain_features, validate_output_type};
use super::helpers::{fetch_contract_constitution, get_sidechain_features, validate_output_type};
use crate::{
chain_storage::{BlockchainBackend, BlockchainDatabase},
transactions::transaction_components::{
Expand All @@ -48,7 +48,7 @@ pub fn validate_acceptance<B: BlockchainBackend>(
let acceptance_features = get_contract_acceptance(sidechain_features)?;
let validator_node_public_key = &acceptance_features.validator_node_public_key;

let constitution = get_contract_constitution(db, contract_id)?;
let constitution = fetch_contract_constitution(db, contract_id)?;

validate_public_key(constitution, validator_node_public_key)?;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright 2022, The Tari Project
//
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
// following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
// disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
// following disclaimer in the documentation and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
// products derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

use tari_common_types::types::FixedHash;
use tari_utilities::hex::Hex;

use super::helpers::{fetch_contract_features, get_sidechain_features, validate_output_type};
use crate::{
chain_storage::{BlockchainBackend, BlockchainDatabase},
transactions::transaction_components::{OutputType, TransactionOutput},
validation::ValidationError,
};

pub fn validate_constitution<B: BlockchainBackend>(
db: &BlockchainDatabase<B>,
output: &TransactionOutput,
) -> Result<(), ValidationError> {
validate_output_type(output, OutputType::ContractConstitution)?;

let sidechain_features = get_sidechain_features(output)?;
let contract_id = sidechain_features.contract_id;

validate_definition_existence(db, contract_id)?;
validate_duplication(db, contract_id)?;

Ok(())
}

fn validate_definition_existence<B: BlockchainBackend>(
db: &BlockchainDatabase<B>,
contract_id: FixedHash,
) -> Result<(), ValidationError> {
match fetch_contract_features(db, contract_id, OutputType::ContractDefinition)? {
Some(_) => Ok(()),
None => {
let msg = format!(
"Contract definition not found for contract_id ({:?})",
contract_id.to_hex()
);
Err(ValidationError::DanLayerError(msg))
},
}
}

fn validate_duplication<B: BlockchainBackend>(
db: &BlockchainDatabase<B>,
contract_id: FixedHash,
) -> Result<(), ValidationError> {
match fetch_contract_features(db, contract_id, OutputType::ContractConstitution)? {
Some(_) => {
let msg = format!(
"Duplicated contract constitution for contract_id ({:?})",
contract_id.to_hex()
);
Err(ValidationError::DanLayerError(msg))
},
None => Ok(()),
}
}

#[cfg(test)]
mod test {
use tari_common_types::types::FixedHash;

use crate::validation::dan_validators::test_helpers::{
assert_dan_error,
create_contract_constitution_schema,
init_test_blockchain,
publish_constitution,
publish_definition,
schema_to_transaction,
};

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

// construct a transaction for a constitution, without a prior definition
let contract_id = FixedHash::default();
let schema = create_contract_constitution_schema(contract_id, change[2].clone(), Vec::new());
let (tx, _) = schema_to_transaction(&schema);

// try to validate the constitution transaction and check that we get the error
assert_dan_error(&blockchain, &tx, "Contract definition not found");
}

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

// publish the contract definition and constitution into a block
let contract_id = publish_definition(&mut blockchain, change[0].clone());
publish_constitution(&mut blockchain, change[1].clone(), contract_id);

// construct a transaction for the duplicated contract constitution
let schema = create_contract_constitution_schema(contract_id, change[2].clone(), Vec::new());
let (tx, _) = schema_to_transaction(&schema);

// try to validate the duplicated constitution transaction and check that we get the error
assert_dan_error(&blockchain, &tx, "Duplicated contract constitution");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
use tari_common_types::types::FixedHash;
use tari_utilities::hex::Hex;

use super::helpers::{get_sidechain_features, validate_output_type};
use super::helpers::{fetch_contract_features, get_sidechain_features, validate_output_type};
use crate::{
chain_storage::{BlockchainBackend, BlockchainDatabase},
transactions::transaction_components::{OutputType, TransactionOutput},
Expand All @@ -48,20 +48,16 @@ fn validate_duplication<B: BlockchainBackend>(
db: &BlockchainDatabase<B>,
contract_id: FixedHash,
) -> Result<(), ValidationError> {
let outputs = db
.fetch_contract_outputs_by_contract_id_and_type(contract_id, OutputType::ContractDefinition)
.map_err(|err| ValidationError::DanLayerError(format!("Could not search outputs: {}", err)))?;

let is_duplicated = !outputs.is_empty();
if is_duplicated {
let msg = format!(
"Duplicated contract definition for contract_id ({:?})",
contract_id.to_hex()
);
return Err(ValidationError::DanLayerError(msg));
match fetch_contract_features(db, contract_id, OutputType::ContractDefinition)? {
Some(_) => {
let msg = format!(
"Duplicated contract definition for contract_id ({:?})",
contract_id.to_hex()
);
Err(ValidationError::DanLayerError(msg))
},
None => Ok(()),
}

Ok(())
}

#[cfg(test)]
Expand Down
78 changes: 40 additions & 38 deletions base_layer/core/src/validation/dan_validators/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

use tari_common_types::types::FixedHash;
use tari_utilities::hex::Hex;

use crate::{
chain_storage::{BlockchainBackend, BlockchainDatabase},
Expand All @@ -44,59 +45,51 @@ pub fn validate_output_type(
Ok(())
}

pub fn get_sidechain_features(output: &TransactionOutput) -> Result<&SideChainFeatures, ValidationError> {
match output.features.sidechain_features.as_ref() {
Some(features) => Ok(features),
None => Err(ValidationError::DanLayerError(
"Sidechain features not found".to_string(),
)),
}
}

pub fn get_contract_constitution<B: BlockchainBackend>(
pub fn fetch_contract_features<B: BlockchainBackend>(
db: &BlockchainDatabase<B>,
contract_id: FixedHash,
) -> Result<ContractConstitution, ValidationError> {
let contract_outputs = db
.fetch_contract_outputs_by_contract_id_and_type(contract_id, OutputType::ContractConstitution)
.unwrap();

if contract_outputs.is_empty() {
return Err(ValidationError::DanLayerError(
"Contract constitution not found".to_string(),
));
output_type: OutputType,
) -> Result<Option<SideChainFeatures>, ValidationError> {
let outputs = db
.fetch_contract_outputs_by_contract_id_and_type(contract_id, output_type)
.map_err(|err| ValidationError::DanLayerError(format!("Could not search outputs: {}", err)))?;
if outputs.is_empty() {
return Ok(None);
}

// we assume that only one constitution should be present in the blockchain for any given contract
// TODO: create a validation to avoid duplicated constitution publishing
let utxo_info = match contract_outputs.first() {
let utxo_info = match outputs.first() {
Some(value) => value,
None => {
return Err(ValidationError::DanLayerError(
"Contract constitution UtxoMindInfo not found".to_string(),
))
},
None => return Ok(None),
};

let constitution_output = match utxo_info.output.as_transaction_output() {
let transaction_output = match utxo_info.output.as_transaction_output() {
Some(value) => value,
None => {
return Err(ValidationError::DanLayerError(
"Contract constitution output not found".to_string(),
))
},
None => return Ok(None),
};

let constitution_features = match constitution_output.features.sidechain_features.as_ref() {
match transaction_output.features.sidechain_features.as_ref() {
Some(value) => Ok(Some(value.clone())),
None => Ok(None),
}
}

pub fn fetch_contract_constitution<B: BlockchainBackend>(
db: &BlockchainDatabase<B>,
contract_id: FixedHash,
) -> Result<ContractConstitution, ValidationError> {
let features_result = fetch_contract_features(db, contract_id, OutputType::ContractConstitution)?;

let features = match features_result {
Some(value) => value,
None => {
return Err(ValidationError::DanLayerError(
"Contract constitution output features not found".to_string(),
))
return Err(ValidationError::DanLayerError(format!(
"Contract constitution not found for contract_id {}",
contract_id.to_hex()
)))
},
};

let constitution = match constitution_features.constitution.as_ref() {
let constitution = match features.constitution.as_ref() {
Some(value) => value,
None => {
return Err(ValidationError::DanLayerError(
Expand All @@ -107,3 +100,12 @@ pub fn get_contract_constitution<B: BlockchainBackend>(

Ok(constitution.clone())
}

pub fn get_sidechain_features(output: &TransactionOutput) -> Result<&SideChainFeatures, ValidationError> {
match output.features.sidechain_features.as_ref() {
Some(features) => Ok(features),
None => Err(ValidationError::DanLayerError(
"Sidechain features not found".to_string(),
)),
}
}
4 changes: 4 additions & 0 deletions base_layer/core/src/validation/dan_validators/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ use crate::{
mod acceptance_validator;
use acceptance_validator::validate_acceptance;

mod constitution_validator;
use constitution_validator::validate_constitution;

mod definition_validator;
use definition_validator::validate_definition;

Expand All @@ -54,6 +57,7 @@ impl<B: BlockchainBackend> MempoolTransactionValidation for TxDanLayerValidator<
for output in tx.body().outputs() {
match output.features.output_type {
OutputType::ContractDefinition => validate_definition(&self.db, output)?,
OutputType::ContractConstitution => validate_constitution(&self.db, output)?,
OutputType::ContractValidatorAcceptance => validate_acceptance(&self.db, output)?,
_ => continue,
}
Expand Down
5 changes: 5 additions & 0 deletions base_layer/core/src/validation/dan_validators/test_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ pub fn publish_definition(blockchain: &mut TestBlockchain, change: UnblindedOutp
contract_id
}

pub fn publish_constitution(blockchain: &mut TestBlockchain, change: UnblindedOutput, contract_id: FixedHash) {
let schema = create_contract_constitution_schema(contract_id, change, Vec::new());
create_block(blockchain, "constitution", schema);
}

pub fn schema_to_transaction(schema: &TransactionSchema) -> (Transaction, Vec<UnblindedOutput>) {
let mut utxos = Vec::new();

Expand Down

0 comments on commit c2efd5e

Please sign in to comment.