Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(base_layer): basic contract constitution validation #4232

Merged
merged 2 commits into from
Jun 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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