From c2efd5e161176d7c66a6669fef2625bf77d2eb82 Mon Sep 17 00:00:00 2001 From: Miguel Naveira <47919901+mrnaveira@users.noreply.github.com> Date: Mon, 27 Jun 2022 14:51:28 +0100 Subject: [PATCH] feat(base_layer): basic contract constitution validation (#4232) 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 --- .../dan_validators/acceptance_validator.rs | 4 +- .../dan_validators/constitution_validator.rs | 123 ++++++++++++++++++ .../dan_validators/definition_validator.rs | 24 ++-- .../src/validation/dan_validators/helpers.rs | 78 +++++------ .../core/src/validation/dan_validators/mod.rs | 4 + .../validation/dan_validators/test_helpers.rs | 5 + 6 files changed, 184 insertions(+), 54 deletions(-) create mode 100644 base_layer/core/src/validation/dan_validators/constitution_validator.rs diff --git a/base_layer/core/src/validation/dan_validators/acceptance_validator.rs b/base_layer/core/src/validation/dan_validators/acceptance_validator.rs index 7119154c0d..b7fb14a9fb 100644 --- a/base_layer/core/src/validation/dan_validators/acceptance_validator.rs +++ b/base_layer/core/src/validation/dan_validators/acceptance_validator.rs @@ -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::{ @@ -48,7 +48,7 @@ pub fn validate_acceptance( 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)?; diff --git a/base_layer/core/src/validation/dan_validators/constitution_validator.rs b/base_layer/core/src/validation/dan_validators/constitution_validator.rs new file mode 100644 index 0000000000..759fc6ac94 --- /dev/null +++ b/base_layer/core/src/validation/dan_validators/constitution_validator.rs @@ -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( + db: &BlockchainDatabase, + 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( + db: &BlockchainDatabase, + 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( + db: &BlockchainDatabase, + 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"); + } +} diff --git a/base_layer/core/src/validation/dan_validators/definition_validator.rs b/base_layer/core/src/validation/dan_validators/definition_validator.rs index 5ea4db3bdf..4fe0a1792d 100644 --- a/base_layer/core/src/validation/dan_validators/definition_validator.rs +++ b/base_layer/core/src/validation/dan_validators/definition_validator.rs @@ -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}, @@ -48,20 +48,16 @@ fn validate_duplication( db: &BlockchainDatabase, 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)] diff --git a/base_layer/core/src/validation/dan_validators/helpers.rs b/base_layer/core/src/validation/dan_validators/helpers.rs index bea70f1f1d..8f582c04f0 100644 --- a/base_layer/core/src/validation/dan_validators/helpers.rs +++ b/base_layer/core/src/validation/dan_validators/helpers.rs @@ -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}, @@ -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( +pub fn fetch_contract_features( db: &BlockchainDatabase, contract_id: FixedHash, -) -> Result { - 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, 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( + db: &BlockchainDatabase, + contract_id: FixedHash, +) -> Result { + 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( @@ -107,3 +100,12 @@ pub fn get_contract_constitution( 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(), + )), + } +} diff --git a/base_layer/core/src/validation/dan_validators/mod.rs b/base_layer/core/src/validation/dan_validators/mod.rs index e28e0669a8..19daf722d9 100644 --- a/base_layer/core/src/validation/dan_validators/mod.rs +++ b/base_layer/core/src/validation/dan_validators/mod.rs @@ -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; @@ -54,6 +57,7 @@ impl 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, } diff --git a/base_layer/core/src/validation/dan_validators/test_helpers.rs b/base_layer/core/src/validation/dan_validators/test_helpers.rs index ffcdc9c9e8..c46eddfa27 100644 --- a/base_layer/core/src/validation/dan_validators/test_helpers.rs +++ b/base_layer/core/src/validation/dan_validators/test_helpers.rs @@ -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) { let mut utxos = Vec::new();