From dbb59758a92cdf4483574dc6e7c719efa94eedfd Mon Sep 17 00:00:00 2001 From: Hansie Odendaal <39146854+hansieodendaal@users.noreply.github.com> Date: Wed, 28 Jun 2023 09:37:42 +0200 Subject: [PATCH] feat: covenants audit (#5526) Description --- - Added missing docs - Expanded some test cases - Added `FeaturesRangeProofType` and `MinimumValuePromise` to `pub enum OutputField` with associated implementations and test cases as those were missing. Motivation and Context --- Preparation for code audit How Has This Been Tested? --- Expanded unit tests for the additional output fields. What process can a PR reviewer use to test or verify this change? --- Review additions Review docs Breaking Changes --- - [x] None - [ ] Requires data directory on base node to be deleted - [ ] Requires hard fork - [ ] Other - Please specify --------- Co-authored-by: SW van Heerden --- base_layer/core/src/covenants/byte_codes.rs | 4 +- base_layer/core/src/covenants/fields.rs | 111 ++++++++++++++---- .../src/covenants/filters/absolute_height.rs | 3 + base_layer/core/src/covenants/filters/and.rs | 18 ++- .../core/src/covenants/filters/field_eq.rs | 3 + .../src/covenants/filters/fields_hashed_eq.rs | 9 ++ .../src/covenants/filters/fields_preserved.rs | 9 +- .../core/src/covenants/filters/filter.rs | 16 +++ .../core/src/covenants/filters/identity.rs | 2 + base_layer/core/src/covenants/filters/not.rs | 2 + base_layer/core/src/covenants/filters/or.rs | 3 + .../src/covenants/filters/output_hash_eq.rs | 3 + base_layer/core/src/covenants/filters/test.rs | 3 + base_layer/core/src/covenants/filters/xor.rs | 4 + 14 files changed, 161 insertions(+), 29 deletions(-) diff --git a/base_layer/core/src/covenants/byte_codes.rs b/base_layer/core/src/covenants/byte_codes.rs index 61eea15b49..686b4af0df 100644 --- a/base_layer/core/src/covenants/byte_codes.rs +++ b/base_layer/core/src/covenants/byte_codes.rs @@ -88,7 +88,9 @@ pub const FIELD_COVENANT: u8 = 0x03; pub const FIELD_FEATURES: u8 = 0x04; pub const FIELD_FEATURES_OUTPUT_TYPE: u8 = 0x05; pub const FIELD_FEATURES_MATURITY: u8 = 0x06; -pub const FIELD_FEATURES_SIDE_CHAIN_FEATURES: u8 = 0x08; +pub const FIELD_FEATURES_SIDE_CHAIN_FEATURES: u8 = 0x07; +pub const FIELD_FEATURES_RANGE_PROOF_TYPE: u8 = 0x08; +pub const MINIMUM_VALUE_PROMISE: u8 = 0x09; #[cfg(test)] mod tests { diff --git a/base_layer/core/src/covenants/fields.rs b/base_layer/core/src/covenants/fields.rs index 669b406e0d..d92ca379ca 100644 --- a/base_layer/core/src/covenants/fields.rs +++ b/base_layer/core/src/covenants/fields.rs @@ -54,6 +54,8 @@ pub enum OutputField { FeaturesOutputType = byte_codes::FIELD_FEATURES_OUTPUT_TYPE, FeaturesMaturity = byte_codes::FIELD_FEATURES_MATURITY, FeaturesSideChainFeatures = byte_codes::FIELD_FEATURES_SIDE_CHAIN_FEATURES, + FeaturesRangeProofType = byte_codes::FIELD_FEATURES_RANGE_PROOF_TYPE, + MinimumValuePromise = byte_codes::MINIMUM_VALUE_PROMISE, } impl OutputField { @@ -70,6 +72,8 @@ impl OutputField { FIELD_FEATURES_OUTPUT_TYPE => Ok(FeaturesOutputType), FIELD_FEATURES_MATURITY => Ok(FeaturesMaturity), FIELD_FEATURES_SIDE_CHAIN_FEATURES => Ok(FeaturesSideChainFeatures), + FIELD_FEATURES_RANGE_PROOF_TYPE => Ok(FeaturesRangeProofType), + MINIMUM_VALUE_PROMISE => Ok(MinimumValuePromise), _ => Err(CovenantDecodeError::UnknownByteCode { code: byte }), } @@ -91,6 +95,8 @@ impl OutputField { FeaturesOutputType => &output.features.output_type as &dyn Any, FeaturesMaturity => &output.features.maturity as &dyn Any, FeaturesSideChainFeatures => &output.features.sidechain_feature as &dyn Any, + FeaturesRangeProofType => &output.features.range_proof_type as &dyn Any, + MinimumValuePromise => &output.minimum_value_promise as &dyn Any, }; val.downcast_ref::() } @@ -109,6 +115,8 @@ impl OutputField { FeaturesOutputType => BorshSerialize::serialize(&output.features.output_type, &mut writer), FeaturesMaturity => BorshSerialize::serialize(&output.features.maturity, &mut writer), FeaturesSideChainFeatures => BorshSerialize::serialize(&output.features.sidechain_feature, &mut writer), + FeaturesRangeProofType => BorshSerialize::serialize(&output.features.range_proof_type, &mut writer), + MinimumValuePromise => BorshSerialize::serialize(&output.minimum_value_promise, &mut writer), } .unwrap(); writer @@ -147,6 +155,14 @@ impl OutputField { .features() .map(|features| features.sidechain_feature == output.features.sidechain_feature) .unwrap_or(false), + FeaturesRangeProofType => input + .features() + .map(|features| features.range_proof_type == output.features.range_proof_type) + .unwrap_or(false), + MinimumValuePromise => input + .minimum_value_promise() + .map(|minimum_value_promise| *minimum_value_promise == output.minimum_value_promise) + .unwrap_or(false), } } @@ -222,11 +238,20 @@ impl OutputField { OutputField::FeaturesMaturity } - #[allow(dead_code)] #[allow(dead_code)] pub fn features_sidechain_feature() -> Self { OutputField::FeaturesSideChainFeatures } + + #[allow(dead_code)] + pub fn features_range_proof_type() -> Self { + OutputField::FeaturesRangeProofType + } + + #[allow(dead_code)] + pub fn minimum_value_promise() -> Self { + OutputField::MinimumValuePromise + } } impl Display for OutputField { @@ -242,6 +267,8 @@ impl Display for OutputField { FeaturesOutputType => write!(f, "field::features_flags"), FeaturesSideChainFeatures => write!(f, "field::features_sidechain_feature"), FeaturesMaturity => write!(f, "field::features_maturity"), + FeaturesRangeProofType => write!(f, "field::features_range_proof_type"), + MinimumValuePromise => write!(f, "field::minimum_value_promise"), } } } @@ -344,7 +371,11 @@ mod test { mod is_eq { use super::*; - use crate::transactions::test_helpers::create_test_core_key_manager_with_memory_db; + use crate::transactions::{ + tari_amount::MicroTari, + test_helpers::create_test_core_key_manager_with_memory_db, + transaction_components::RangeProofType, + }; #[tokio::test] async fn it_returns_true_if_eq() { @@ -366,20 +397,26 @@ mod test { .remove(0); assert!(OutputField::Commitment.is_eq(&output, &output.commitment).unwrap()); - assert!(OutputField::Features.is_eq(&output, &output.features).unwrap()); assert!(OutputField::Script.is_eq(&output, &output.script).unwrap()); - assert!(OutputField::Covenant.is_eq(&output, &output.covenant).unwrap()); - assert!(OutputField::FeaturesMaturity - .is_eq(&output, &output.features.maturity) + assert!(OutputField::SenderOffsetPublicKey + .is_eq(&output, &output.sender_offset_public_key) .unwrap()); + assert!(OutputField::Covenant.is_eq(&output, &output.covenant).unwrap()); + assert!(OutputField::Features.is_eq(&output, &output.features).unwrap()); assert!(OutputField::FeaturesOutputType .is_eq(&output, &output.features.output_type) .unwrap()); + assert!(OutputField::FeaturesMaturity + .is_eq(&output, &output.features.maturity) + .unwrap()); assert!(OutputField::FeaturesSideChainFeatures .is_eq(&output, output.features.sidechain_feature.as_ref().unwrap()) .unwrap()); - assert!(OutputField::SenderOffsetPublicKey - .is_eq(&output, &output.sender_offset_public_key) + assert!(OutputField::FeaturesRangeProofType + .is_eq(&output, &output.features.range_proof_type) + .unwrap()); + assert!(OutputField::MinimumValuePromise + .is_eq(&output, &output.minimum_value_promise) .unwrap()); } @@ -392,9 +429,13 @@ mod test { UtxoTestParams { features: OutputFeatures { sidechain_feature: Some(side_chain_features), + range_proof_type: RangeProofType::RevealedValue, + output_type: OutputType::Burn, ..Default::default() }, script: script![Drop Nop], + minimum_value_promise: MicroTari(123456), + value: MicroTari(123456), ..Default::default() }, &key_manager, @@ -403,19 +444,25 @@ mod test { .remove(0); assert!(!OutputField::Commitment.is_eq(&output, &Commitment::default()).unwrap()); - assert!(!OutputField::Features - .is_eq(&output, &OutputFeatures::default()) - .unwrap()); assert!(!OutputField::Script.is_eq(&output, &script![Nop Drop]).unwrap()); + assert!(!OutputField::SenderOffsetPublicKey + .is_eq(&output, &PublicKey::default()) + .unwrap()); assert!(!OutputField::Covenant .is_eq(&output, &covenant!(and(identity(), identity()))) .unwrap()); + assert!(!OutputField::Features + .is_eq(&output, &OutputFeatures::default()) + .unwrap()); assert!(!OutputField::FeaturesMaturity.is_eq(&output, &123u64).unwrap()); assert!(!OutputField::FeaturesOutputType .is_eq(&output, &OutputType::Coinbase) .unwrap()); - assert!(!OutputField::SenderOffsetPublicKey - .is_eq(&output, &PublicKey::default()) + assert!(!OutputField::FeaturesRangeProofType + .is_eq(&output, &RangeProofType::BulletProofPlus) + .unwrap()); + assert!(!OutputField::MinimumValuePromise + .is_eq(&output, &MicroTari::default()) .unwrap()); } } @@ -459,13 +506,15 @@ mod test { } assert!(OutputField::Commitment.is_eq_input(&input, &output)); - assert!(OutputField::Features.is_eq_input(&input, &output)); assert!(OutputField::Script.is_eq_input(&input, &output)); + assert!(OutputField::SenderOffsetPublicKey.is_eq_input(&input, &output)); assert!(OutputField::Covenant.is_eq_input(&input, &output)); + assert!(OutputField::Features.is_eq_input(&input, &output)); assert!(OutputField::FeaturesMaturity.is_eq_input(&input, &output)); assert!(OutputField::FeaturesOutputType.is_eq_input(&input, &output)); assert!(OutputField::FeaturesSideChainFeatures.is_eq_input(&input, &output)); - assert!(OutputField::SenderOffsetPublicKey.is_eq_input(&input, &output)); + assert!(OutputField::FeaturesRangeProofType.is_eq_input(&input, &output)); + assert!(OutputField::MinimumValuePromise.is_eq_input(&input, &output)); } } @@ -473,13 +522,15 @@ mod test { fn display() { let output_fields = [ OutputField::Commitment, + OutputField::Script, + OutputField::SenderOffsetPublicKey, + OutputField::Covenant, OutputField::Features, + OutputField::FeaturesMaturity, OutputField::FeaturesOutputType, OutputField::FeaturesSideChainFeatures, - OutputField::FeaturesMaturity, - OutputField::SenderOffsetPublicKey, - OutputField::Script, - OutputField::Covenant, + OutputField::FeaturesRangeProofType, + OutputField::MinimumValuePromise, ]; output_fields.iter().for_each(|f| { assert!(f.to_string().starts_with("field::")); @@ -495,7 +546,11 @@ mod test { use tari_crypto::hashing::DomainSeparation; use super::*; - use crate::transactions::test_helpers::create_test_core_key_manager_with_memory_db; + use crate::transactions::{ + tari_amount::MicroTari, + test_helpers::create_test_core_key_manager_with_memory_db, + transaction_components::RangeProofType, + }; #[tokio::test] async fn it_constructs_challenge_using_consensus_encoding() { @@ -503,6 +558,7 @@ mod test { let features = OutputFeatures { maturity: 42, output_type: OutputType::Coinbase, + range_proof_type: RangeProofType::RevealedValue, ..Default::default() }; let output = create_outputs( @@ -510,6 +566,8 @@ mod test { UtxoTestParams { features, script: script![Drop Nop], + minimum_value_promise: MicroTari(123456), + value: MicroTari(123456), ..Default::default() }, &key_manager, @@ -521,6 +579,7 @@ mod test { fields.push(OutputField::Features); fields.push(OutputField::Commitment); fields.push(OutputField::Script); + fields.push(OutputField::MinimumValuePromise); let hash = fields.construct_challenge_from(&output).finalize(); let hash = hash.to_vec(); @@ -530,6 +589,7 @@ mod test { .chain(output.features.try_to_vec().unwrap()) .chain(output.commitment.try_to_vec().unwrap()) .chain(output.script.try_to_vec().unwrap()) + .chain(output.minimum_value_promise.try_to_vec().unwrap()) .finalize() .to_vec(); assert_eq!(hash, expected_hash); @@ -538,19 +598,26 @@ mod test { mod get_field_value_ref { use super::*; - use crate::transactions::test_helpers::create_test_core_key_manager_with_memory_db; + use crate::transactions::{ + tari_amount::MicroTari, + test_helpers::create_test_core_key_manager_with_memory_db, + transaction_components::RangeProofType, + }; #[tokio::test] async fn it_retrieves_the_value_as_ref() { let key_manager = create_test_core_key_manager_with_memory_db(); let features = OutputFeatures { maturity: 42, + range_proof_type: RangeProofType::RevealedValue, ..Default::default() }; let output = create_outputs( 1, UtxoTestParams { features: features.clone(), + minimum_value_promise: MicroTari(123456), + value: MicroTari(123456), ..Default::default() }, &key_manager, @@ -560,6 +627,8 @@ mod test { .unwrap(); let r = OutputField::Features.get_field_value_ref::(&output); assert_eq!(*r.unwrap(), features); + let r = OutputField::MinimumValuePromise.get_field_value_ref::(&output); + assert_eq!(*r.unwrap(), MicroTari(123456)); } } } diff --git a/base_layer/core/src/covenants/filters/absolute_height.rs b/base_layer/core/src/covenants/filters/absolute_height.rs index 175a3ec0b9..c990ecf9e7 100644 --- a/base_layer/core/src/covenants/filters/absolute_height.rs +++ b/base_layer/core/src/covenants/filters/absolute_height.rs @@ -44,10 +44,13 @@ use crate::covenants::{context::CovenantContext, error::CovenantError, filters::Filter, output_set::OutputSet}; +/// Holding struct for the "absolute height" filter #[derive(Debug, Clone, PartialEq, Eq)] pub struct AbsoluteHeightFilter; impl Filter for AbsoluteHeightFilter { + // The absolute height filter removes all outputs in the mutable output set if the current block height is less than + // the absolute height provided in the covenant context. fn filter(&self, context: &mut CovenantContext<'_>, output_set: &mut OutputSet<'_>) -> Result<(), CovenantError> { let abs_height = context.next_arg()?.require_uint()?; let current_height = context.block_height(); diff --git a/base_layer/core/src/covenants/filters/and.rs b/base_layer/core/src/covenants/filters/and.rs index 87c6741949..cd4533244d 100644 --- a/base_layer/core/src/covenants/filters/and.rs +++ b/base_layer/core/src/covenants/filters/and.rs @@ -22,10 +22,13 @@ use crate::covenants::{context::CovenantContext, error::CovenantError, filters::Filter, output_set::OutputSet}; +/// Holding struct for the "and" filter #[derive(Debug, Clone, PartialEq, Eq)] pub struct AndFilter; impl Filter for AndFilter { + // The and filter removes all outputs in the mutable output set based on the next two filters in the covenant + // context - the filters are applied in order. fn filter(&self, context: &mut CovenantContext<'_>, output_set: &mut OutputSet<'_>) -> Result<(), CovenantError> { let a = context.require_next_filter()?; a.filter(context, output_set)?; @@ -51,19 +54,26 @@ mod test { #[tokio::test] async fn it_filters_outputset_using_intersection() { let key_manager = create_test_core_key_manager_with_memory_db(); - let script = script!(Nop); - let covenant = - covenant!(and(field_eq(@field::features_maturity, @uint(42),), field_eq(@field::script, @script(script)))); + let script = script!(CheckHeight(101)); + let covenant = covenant!(and(field_eq(@field::features_maturity, @uint(42),), field_eq(@field::script, @script(script.clone())))); let input = create_input(&key_manager).await; let (mut context, outputs) = setup_filter_test( &covenant, &input, 0, |outputs| { + // output satisfying maturity only + outputs[2].features.maturity = 42; + outputs[2].script = script!(CheckHeight(102)); + // output satisfying maturity and script outputs[5].features.maturity = 42; + outputs[5].script = script.clone(); + // output satisfying maturity and script outputs[7].features.maturity = 42; - // Does not have maturity = 42 + outputs[7].script = script.clone(); + // output satisfying script only outputs[8].features.maturity = 123; + outputs[8].script = script.clone(); }, &key_manager, ) diff --git a/base_layer/core/src/covenants/filters/field_eq.rs b/base_layer/core/src/covenants/filters/field_eq.rs index 973baa5f4a..9d8180c636 100644 --- a/base_layer/core/src/covenants/filters/field_eq.rs +++ b/base_layer/core/src/covenants/filters/field_eq.rs @@ -28,10 +28,13 @@ use crate::covenants::{ output_set::OutputSet, }; +/// Holding struct for the "fields equal" filter #[derive(Debug, Clone, PartialEq, Eq)] pub struct FieldEqFilter; impl Filter for FieldEqFilter { + // Filters out all outputs that do not have the specified output field equal to the specified value based on the + // next two arguments in the covenant context. fn filter(&self, context: &mut CovenantContext<'_>, output_set: &mut OutputSet<'_>) -> Result<(), CovenantError> { let field = context.next_arg()?.require_outputfield()?; let arg = context.next_arg()?; diff --git a/base_layer/core/src/covenants/filters/fields_hashed_eq.rs b/base_layer/core/src/covenants/filters/fields_hashed_eq.rs index 87a97e95f2..8c29b209b6 100644 --- a/base_layer/core/src/covenants/filters/fields_hashed_eq.rs +++ b/base_layer/core/src/covenants/filters/fields_hashed_eq.rs @@ -23,10 +23,14 @@ use digest::Digest; use crate::covenants::{context::CovenantContext, error::CovenantError, filters::Filter, output_set::OutputSet}; + +/// Holding struct for the "output fields that hash to a given hash" filter #[derive(Debug, Clone, PartialEq, Eq)] pub struct FieldsHashedEqFilter; impl Filter for FieldsHashedEqFilter { + // Filters out all outputs that do not have the hashed output field equal to the specified hash value + // based on the next two arguments in the covenant context. fn filter(&self, context: &mut CovenantContext<'_>, output_set: &mut OutputSet<'_>) -> Result<(), CovenantError> { let fields = context.next_arg()?.require_outputfields()?; let hash = context.next_arg()?.require_hash()?; @@ -78,6 +82,11 @@ mod test { 0, |outputs| { outputs[5].features = features.clone(); + outputs[6].features = OutputFeatures { + maturity: 41, + sidechain_feature: Some(make_sample_sidechain_feature()), + ..Default::default() + }; outputs[7].features = features; }, &key_manager, diff --git a/base_layer/core/src/covenants/filters/fields_preserved.rs b/base_layer/core/src/covenants/filters/fields_preserved.rs index a95d78b67f..2ecd2bacbe 100644 --- a/base_layer/core/src/covenants/filters/fields_preserved.rs +++ b/base_layer/core/src/covenants/filters/fields_preserved.rs @@ -22,10 +22,13 @@ use crate::covenants::{context::CovenantContext, error::CovenantError, filters::Filter, output_set::OutputSet}; +/// Holding struct for the "output fields preserved" filter #[derive(Debug, Clone, PartialEq, Eq)] pub struct FieldsPreservedFilter; impl Filter for FieldsPreservedFilter { + // Filters out all outputs that do not duplicate the specified input field in the covenant context for each output + // in the set. fn filter(&self, context: &mut CovenantContext<'_>, output_set: &mut OutputSet<'_>) -> Result<(), CovenantError> { let fields = context.next_arg()?.require_outputfields()?; let input = context.input(); @@ -50,16 +53,16 @@ mod test { let key_manager = create_test_core_key_manager_with_memory_db(); let mut input = create_input(&key_manager).await; input.set_maturity(42).unwrap(); - input.features_mut().unwrap().output_type = OutputType::Standard; + input.features_mut().unwrap().output_type = OutputType::ValidatorNodeRegistration; let (mut context, outputs) = setup_filter_test( &covenant, &input, 0, |outputs| { outputs[5].features.maturity = 42; - outputs[5].features.output_type = OutputType::Standard; + outputs[5].features.output_type = OutputType::ValidatorNodeRegistration; outputs[7].features.maturity = 42; - outputs[7].features.output_type = OutputType::Standard; + outputs[7].features.output_type = OutputType::ValidatorNodeRegistration; outputs[8].features.maturity = 42; outputs[8].features.output_type = OutputType::Coinbase; }, diff --git a/base_layer/core/src/covenants/filters/filter.rs b/base_layer/core/src/covenants/filters/filter.rs index 00adca87d1..0cdfc286b9 100644 --- a/base_layer/core/src/covenants/filters/filter.rs +++ b/base_layer/core/src/covenants/filters/filter.rs @@ -43,10 +43,12 @@ use crate::covenants::{ output_set::OutputSet, }; +/// The filter trait is implemented by all covenant filters. pub trait Filter { fn filter(&self, context: &mut CovenantContext<'_>, output_set: &mut OutputSet<'_>) -> Result<(), CovenantError>; } +/// A covenant filter is a filter that can be used in a covenant. #[derive(Debug, Clone, PartialEq, Eq)] pub enum CovenantFilter { Identity(IdentityFilter), @@ -62,10 +64,12 @@ pub enum CovenantFilter { } impl CovenantFilter { + /// Determine if the given byte code is a valid filter code. pub fn is_valid_code(code: u8) -> bool { byte_codes::is_valid_filter_code(code) } + /// Write the filter to the given writer as byte code. pub fn write_to(&self, writer: &mut W) -> Result<(), io::Error> { writer.write_u8_fixed(self.as_byte_code())?; Ok(()) @@ -90,6 +94,7 @@ impl CovenantFilter { } } + /// Try to create a covenant filter from the given byte code. pub fn try_from_byte_code(code: u8) -> Result { use byte_codes::*; match code { @@ -107,48 +112,59 @@ impl CovenantFilter { } } + /// Return the "identity" covenant filter. pub fn identity() -> Self { CovenantFilter::Identity(IdentityFilter) } + /// Return the "and" covenant filter. pub fn and() -> Self { CovenantFilter::And(AndFilter) } + /// Return the "or" covenant filter. pub fn or() -> Self { CovenantFilter::Or(OrFilter) } + /// Return the "xor" covenant filter. pub fn xor() -> Self { CovenantFilter::Xor(XorFilter) } + /// Return the "not" covenant filter. pub fn not() -> Self { CovenantFilter::Not(NotFilter) } + /// Return the "output hash eq" covenant filter. pub fn output_hash_eq() -> Self { CovenantFilter::OutputHashEq(OutputHashEqFilter) } + /// Return the "fields preserved" covenant filter. pub fn fields_preserved() -> Self { CovenantFilter::FieldsPreserved(FieldsPreservedFilter) } + /// Return the "field eq" covenant filter. pub fn field_eq() -> Self { CovenantFilter::FieldEq(FieldEqFilter) } + /// Return the "fields hashed eq" covenant filter. pub fn fields_hashed_eq() -> Self { CovenantFilter::FieldsHashedEq(FieldsHashedEqFilter) } + /// Return the "absolute height" covenant filter. pub fn absolute_height() -> Self { CovenantFilter::AbsoluteHeight(AbsoluteHeightFilter) } } impl Filter for CovenantFilter { + // Filter the given output set using the filter specified by the covenant context. fn filter(&self, context: &mut CovenantContext<'_>, output_set: &mut OutputSet<'_>) -> Result<(), CovenantError> { #[allow(clippy::enum_glob_use)] use CovenantFilter::*; diff --git a/base_layer/core/src/covenants/filters/identity.rs b/base_layer/core/src/covenants/filters/identity.rs index 9ef2ee3721..1785dd2a8c 100644 --- a/base_layer/core/src/covenants/filters/identity.rs +++ b/base_layer/core/src/covenants/filters/identity.rs @@ -22,10 +22,12 @@ use crate::covenants::{context::CovenantContext, error::CovenantError, filters::Filter, output_set::OutputSet}; +/// Holding struct for the "identity" filter #[derive(Debug, Clone, PartialEq, Eq)] pub struct IdentityFilter; impl Filter for IdentityFilter { + /// The identity filter does not filter the output set. fn filter(&self, _: &mut CovenantContext<'_>, _: &mut OutputSet<'_>) -> Result<(), CovenantError> { Ok(()) } diff --git a/base_layer/core/src/covenants/filters/not.rs b/base_layer/core/src/covenants/filters/not.rs index e09a5955a3..0d5d71a58c 100644 --- a/base_layer/core/src/covenants/filters/not.rs +++ b/base_layer/core/src/covenants/filters/not.rs @@ -22,10 +22,12 @@ use crate::covenants::{context::CovenantContext, error::CovenantError, filters::Filter, output_set::OutputSet}; +/// Holding struct for the "not" filter #[derive(Debug, Clone, PartialEq, Eq)] pub struct NotFilter; impl Filter for NotFilter { + /// The not filter removes all outputs in the covenant context that are not in the compliment of the filter. fn filter(&self, context: &mut CovenantContext<'_>, output_set: &mut OutputSet<'_>) -> Result<(), CovenantError> { let filter = context.require_next_filter()?; let mut output_set_copy = output_set.clone(); diff --git a/base_layer/core/src/covenants/filters/or.rs b/base_layer/core/src/covenants/filters/or.rs index 8aaaf390b1..07ad183efd 100644 --- a/base_layer/core/src/covenants/filters/or.rs +++ b/base_layer/core/src/covenants/filters/or.rs @@ -22,10 +22,13 @@ use crate::covenants::{context::CovenantContext, error::CovenantError, filters::Filter, output_set::OutputSet}; +/// Holding struct for the "or" filter #[derive(Debug, Clone, PartialEq, Eq)] pub struct OrFilter; impl Filter for OrFilter { + // The or filter only removes outputs in the mutable output set that are removed when applying both filters in the + // covenant context independently from each other. The union of the two outputs sets are returned. fn filter(&self, context: &mut CovenantContext<'_>, output_set: &mut OutputSet<'_>) -> Result<(), CovenantError> { let a = context.require_next_filter()?; let mut output_set_a = output_set.clone(); diff --git a/base_layer/core/src/covenants/filters/output_hash_eq.rs b/base_layer/core/src/covenants/filters/output_hash_eq.rs index 23fe158e2a..46cf3117a3 100644 --- a/base_layer/core/src/covenants/filters/output_hash_eq.rs +++ b/base_layer/core/src/covenants/filters/output_hash_eq.rs @@ -22,10 +22,13 @@ use crate::covenants::{context::CovenantContext, error::CovenantError, filters::Filter, output_set::OutputSet}; +/// Holding struct for the "output hash equal" filter #[derive(Debug, Clone, PartialEq, Eq)] pub struct OutputHashEqFilter; impl Filter for OutputHashEqFilter { + // The output hash equal filter searches for the hashed output field equal to the specified hash value + // based on the covenant context; either returning the output or nothing fn filter(&self, context: &mut CovenantContext<'_>, output_set: &mut OutputSet<'_>) -> Result<(), CovenantError> { let hash = context.next_arg()?.require_hash()?; // An output's hash is unique so the output set is either 1 or 0 outputs will match diff --git a/base_layer/core/src/covenants/filters/test.rs b/base_layer/core/src/covenants/filters/test.rs index f1f5cabce6..a02f1445a8 100644 --- a/base_layer/core/src/covenants/filters/test.rs +++ b/base_layer/core/src/covenants/filters/test.rs @@ -32,6 +32,9 @@ use crate::{ }, }; +/// Create a covenant context and outputs for testing a filter with a given covenant, input and block height. The +/// outputs are default random and modified by closure parameter `output_mod: F` (anonymous function) before it is +/// returned. pub async fn setup_filter_test<'a, F>( covenant: &Covenant, input: &'a TransactionInput, diff --git a/base_layer/core/src/covenants/filters/xor.rs b/base_layer/core/src/covenants/filters/xor.rs index a51e581bff..c6520395b7 100644 --- a/base_layer/core/src/covenants/filters/xor.rs +++ b/base_layer/core/src/covenants/filters/xor.rs @@ -22,10 +22,14 @@ use crate::covenants::{context::CovenantContext, error::CovenantError, filters::Filter, output_set::OutputSet}; +/// Holding struct for the "xor" filter #[derive(Debug, Clone, PartialEq, Eq)] pub struct XorFilter; impl Filter for XorFilter { + // The xor filter removes outputs in the mutable output set that are removed when applying both filters in the + // covenant context independently from each other, but only returns outputs that are in either of the two sets (not + // in both). The symmetric difference of the two filtered sets are returned. fn filter(&self, context: &mut CovenantContext<'_>, output_set: &mut OutputSet<'_>) -> Result<(), CovenantError> { let a = context.require_next_filter()?; let mut output_set_a = output_set.clone();