Skip to content

Commit

Permalink
fix(core/covenants)!: update covenants to support OutputType enum (#4472
Browse files Browse the repository at this point in the history
)

Description
---
- adds new output type arg type
- adds field_eq support for filtering by output type 
- adds new test to check this case

Motivation and Context
---
- Covenants were still using the old `output_flags` field name 
- an OutputType argument literal was needed as filters like field_eq were not able to compare OutputTypes

The following syntax is supported:
`field_eq(@field::features_output_type, @output_type(Burn))`

How Has This Been Tested?
---
Additional unit tests
  • Loading branch information
sdbondi authored Aug 18, 2022
1 parent a481a06 commit e21dfdb
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 35 deletions.
19 changes: 17 additions & 2 deletions base_layer/core/src/covenants/arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ use crate::{
error::CovenantError,
fields::{OutputField, OutputFields},
},
transactions::transaction_components::OutputType,
};

const MAX_COVENANT_ARG_SIZE: usize = 4096;
Expand All @@ -52,6 +53,7 @@ pub enum CovenantArg {
Commitment(Commitment),
TariScript(TariScript),
Covenant(Covenant),
OutputType(OutputType),
Uint(u64),
OutputField(OutputField),
OutputFields(OutputFields),
Expand Down Expand Up @@ -88,6 +90,10 @@ impl CovenantArg {
let covenant = Covenant::from_bytes(&buf)?;
Ok(CovenantArg::Covenant(covenant))
},
ARG_OUTPUT_TYPE => {
let output_type = OutputType::consensus_decode(reader)?;
Ok(CovenantArg::OutputType(output_type))
},
ARG_UINT => {
let v = u64::consensus_decode(reader)?;
Ok(CovenantArg::Uint(v))
Expand Down Expand Up @@ -117,7 +123,8 @@ impl CovenantArg {

pub fn write_to<W: io::Write>(&self, writer: &mut W) -> Result<(), io::Error> {
use byte_codes::*;
use CovenantArg::{Bytes, Commitment, Covenant, Hash, OutputField, OutputFields, PublicKey, TariScript, Uint};
#[allow(clippy::enum_glob_use)]
use CovenantArg::*;

match self {
Hash(hash) => {
Expand All @@ -142,6 +149,10 @@ impl CovenantArg {
writer.write_varint(len)?;
covenant.write_to(writer)?;
},
OutputType(output_type) => {
writer.write_u8_fixed(ARG_OUTPUT_TYPE)?;
output_type.consensus_encode(writer)?;
},
Uint(int) => {
writer.write_u8_fixed(ARG_UINT)?;
int.consensus_encode(writer)?;
Expand Down Expand Up @@ -193,6 +204,8 @@ impl CovenantArg {

require_x_impl!(require_covenant, Covenant, "covenant");

require_x_impl!(require_output_type, OutputType, "output_type");

require_x_impl!(require_outputfield, OutputField, "outputfield");

require_x_impl!(require_outputfields, OutputFields, "outputfields");
Expand Down Expand Up @@ -220,7 +233,8 @@ impl CovenantArg {

impl Display for CovenantArg {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
use CovenantArg::{Bytes, Commitment, Covenant, Hash, OutputField, OutputFields, PublicKey, TariScript, Uint};
#[allow(clippy::enum_glob_use)]
use CovenantArg::*;
match self {
Hash(hash) => write!(f, "Hash({})", to_hex(&hash[..])),
PublicKey(public_key) => write!(f, "PublicKey({})", public_key.to_hex()),
Expand All @@ -231,6 +245,7 @@ impl Display for CovenantArg {
OutputField(field) => write!(f, "OutputField({})", field.as_byte()),
OutputFields(fields) => write!(f, "OutputFields({} field(s))", fields.len()),
Bytes(bytes) => write!(f, "Bytes({} byte(s))", bytes.len()),
OutputType(output_type) => write!(f, "OutputType({})", output_type),
}
}
}
Expand Down
6 changes: 4 additions & 2 deletions base_layer/core/src/covenants/byte_codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub(super) fn is_valid_arg_code(code: u8) -> bool {
ALL_ARGS.contains(&code)
}

pub(super) const ALL_ARGS: [u8; 9] = [
pub(super) const ALL_ARGS: [u8; 10] = [
ARG_HASH,
ARG_PUBLIC_KEY,
ARG_COMMITMENT,
Expand All @@ -35,6 +35,7 @@ pub(super) const ALL_ARGS: [u8; 9] = [
ARG_OUTPUT_FIELD,
ARG_OUTPUT_FIELDS,
ARG_BYTES,
ARG_OUTPUT_TYPE,
];

pub const ARG_HASH: u8 = 0x01;
Expand All @@ -46,6 +47,7 @@ pub const ARG_UINT: u8 = 0x06;
pub const ARG_OUTPUT_FIELD: u8 = 0x07;
pub const ARG_OUTPUT_FIELDS: u8 = 0x08;
pub const ARG_BYTES: u8 = 0x09;
pub const ARG_OUTPUT_TYPE: u8 = 0x0a;

//---------------------------------- FILTER byte codes --------------------------------------------//

Expand Down Expand Up @@ -84,7 +86,7 @@ pub const FIELD_SCRIPT: u8 = 0x01;
pub const FIELD_SENDER_OFFSET_PUBLIC_KEY: u8 = 0x02;
pub const FIELD_COVENANT: u8 = 0x03;
pub const FIELD_FEATURES: u8 = 0x04;
pub const FIELD_FEATURES_FLAGS: u8 = 0x05;
pub const FIELD_FEATURES_OUTPUT_TYPE: u8 = 0x05;
pub const FIELD_FEATURES_MATURITY: u8 = 0x06;
pub const FIELD_FEATURES_METADATA: u8 = 0x07;
pub const FIELD_FEATURES_SIDE_CHAIN_FEATURES: u8 = 0x08;
Expand Down
2 changes: 1 addition & 1 deletion base_layer/core/src/covenants/covenant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ mod test {
let mut input = create_input();
input.set_maturity(42).unwrap();
let covenant = covenant!(fields_preserved(@fields(
@field::features_flags,
@field::features_output_type,
@field::features_maturity,
@field::features_metadata))
);
Expand Down
24 changes: 12 additions & 12 deletions base_layer/core/src/covenants/fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ pub enum OutputField {
SenderOffsetPublicKey = byte_codes::FIELD_SENDER_OFFSET_PUBLIC_KEY,
Covenant = byte_codes::FIELD_COVENANT,
Features = byte_codes::FIELD_FEATURES,
FeaturesFlags = byte_codes::FIELD_FEATURES_FLAGS,
FeaturesOutputType = byte_codes::FIELD_FEATURES_OUTPUT_TYPE,
FeaturesMaturity = byte_codes::FIELD_FEATURES_MATURITY,
FeaturesMetadata = byte_codes::FIELD_FEATURES_METADATA,
FeaturesSideChainFeatures = byte_codes::FIELD_FEATURES_SIDE_CHAIN_FEATURES,
Expand All @@ -67,7 +67,7 @@ impl OutputField {
FIELD_SENDER_OFFSET_PUBLIC_KEY => Ok(SenderOffsetPublicKey),
FIELD_COVENANT => Ok(Covenant),
FIELD_FEATURES => Ok(Features),
FIELD_FEATURES_FLAGS => Ok(FeaturesFlags),
FIELD_FEATURES_OUTPUT_TYPE => Ok(FeaturesOutputType),
FIELD_FEATURES_MATURITY => Ok(FeaturesMaturity),
FIELD_FEATURES_SIDE_CHAIN_FEATURES => Ok(FeaturesSideChainFeatures),
FIELD_FEATURES_METADATA => Ok(FeaturesMetadata),
Expand All @@ -89,7 +89,7 @@ impl OutputField {
SenderOffsetPublicKey => &output.sender_offset_public_key as &dyn Any,
Covenant => &output.covenant as &dyn Any,
Features => &output.features as &dyn Any,
FeaturesFlags => &output.features.output_type as &dyn Any,
FeaturesOutputType => &output.features.output_type as &dyn Any,
FeaturesMaturity => &output.features.maturity as &dyn Any,
FeaturesSideChainFeatures => &output.features.sidechain_features as &dyn Any,
FeaturesMetadata => &output.features.metadata as &dyn Any,
Expand All @@ -106,7 +106,7 @@ impl OutputField {
SenderOffsetPublicKey => output.sender_offset_public_key.to_consensus_bytes(),
Covenant => output.covenant.to_consensus_bytes(),
Features => output.features.to_consensus_bytes(),
FeaturesFlags => output.features.output_type.to_consensus_bytes(),
FeaturesOutputType => output.features.output_type.to_consensus_bytes(),
FeaturesMaturity => output.features.maturity.to_consensus_bytes(),
FeaturesSideChainFeatures => output.features.sidechain_features.to_consensus_bytes(),
FeaturesMetadata => output.features.metadata.to_consensus_bytes(),
Expand Down Expand Up @@ -134,7 +134,7 @@ impl OutputField {
.features()
.map(|features| *features == output.features)
.unwrap_or(false),
FeaturesFlags => input
FeaturesOutputType => input
.features()
.map(|features| features.output_type == output.features.output_type)
.unwrap_or(false),
Expand Down Expand Up @@ -216,8 +216,8 @@ impl OutputField {
}

#[allow(dead_code)]
pub fn features_flags() -> Self {
OutputField::FeaturesFlags
pub fn features_output_type() -> Self {
OutputField::FeaturesOutputType
}

#[allow(dead_code)]
Expand Down Expand Up @@ -247,7 +247,7 @@ impl Display for OutputField {
Script => write!(f, "field::script"),
Covenant => write!(f, "field::covenant"),
Features => write!(f, "field::features"),
FeaturesFlags => write!(f, "field::features_flags"),
FeaturesOutputType => write!(f, "field::features_flags"),
FeaturesSideChainFeatures => write!(f, "field::features_sidechain_features"),
FeaturesMetadata => write!(f, "field::features_metadata"),
FeaturesMaturity => write!(f, "field::features_maturity"),
Expand Down Expand Up @@ -373,7 +373,7 @@ mod test {
assert!(OutputField::FeaturesMaturity
.is_eq(&output, &output.features.maturity)
.unwrap());
assert!(OutputField::FeaturesFlags
assert!(OutputField::FeaturesOutputType
.is_eq(&output, &output.features.output_type)
.unwrap());
assert!(OutputField::FeaturesSideChainFeatures
Expand Down Expand Up @@ -411,7 +411,7 @@ mod test {
.is_eq(&output, &covenant!(and(identity(), identity())))
.unwrap());
assert!(!OutputField::FeaturesMaturity.is_eq(&output, &123u64).unwrap());
assert!(!OutputField::FeaturesFlags
assert!(!OutputField::FeaturesOutputType
.is_eq(&output, &OutputType::Coinbase)
.unwrap());
assert!(!OutputField::FeaturesMetadata.is_eq(&output, &vec![123u8]).unwrap());
Expand Down Expand Up @@ -457,7 +457,7 @@ mod test {
assert!(OutputField::Script.is_eq_input(&input, &output));
assert!(OutputField::Covenant.is_eq_input(&input, &output));
assert!(OutputField::FeaturesMaturity.is_eq_input(&input, &output));
assert!(OutputField::FeaturesFlags.is_eq_input(&input, &output));
assert!(OutputField::FeaturesOutputType.is_eq_input(&input, &output));
assert!(OutputField::FeaturesSideChainFeatures.is_eq_input(&input, &output));
assert!(OutputField::FeaturesMetadata.is_eq_input(&input, &output));
assert!(OutputField::SenderOffsetPublicKey.is_eq_input(&input, &output));
Expand All @@ -469,7 +469,7 @@ mod test {
let output_fields = [
OutputField::Commitment,
OutputField::Features,
OutputField::FeaturesFlags,
OutputField::FeaturesOutputType,
OutputField::FeaturesSideChainFeatures,
OutputField::FeaturesMetadata,
OutputField::FeaturesMaturity,
Expand Down
19 changes: 19 additions & 0 deletions base_layer/core/src/covenants/filters/field_eq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ impl Filter for FieldEqFilter {
Commitment(commitment) => field.is_eq(output, commitment),
TariScript(script) => field.is_eq(output, script),
Covenant(covenant) => field.is_eq(output, covenant),
OutputType(output_type) => field.is_eq(output, output_type),
Uint(int) => {
let val = field
.get_field_value_ref::<u64>(output)
Expand Down Expand Up @@ -81,6 +82,7 @@ mod test {
use crate::{
covenant,
covenants::test::{create_context, create_input, create_outputs},
transactions::transaction_components::OutputType,
};

#[test]
Expand Down Expand Up @@ -180,6 +182,23 @@ mod test {
assert_eq!(output_set.get_selected_indexes(), vec![5, 7]);
}

#[test]
fn it_filters_output_type() {
let covenant = covenant!(field_eq(@field::features_output_type, @output_type(Coinbase)));
let input = create_input();
let mut context = create_context(&covenant, &input, 0);
// Remove `field_eq`
context.next_filter().unwrap();
let mut outputs = create_outputs(10, Default::default());
outputs[5].features.output_type = OutputType::Coinbase;
outputs[7].features.output_type = OutputType::Coinbase;
let mut output_set = OutputSet::new(&outputs);
FieldEqFilter.filter(&mut context, &mut output_set).unwrap();

assert_eq!(output_set.len(), 2);
assert_eq!(output_set.get_selected_indexes(), vec![5, 7]);
}

#[test]
fn it_errors_if_field_has_an_incorrect_type() {
let covenant = covenant!(field_eq(@field::features, @uint(42)));
Expand Down
2 changes: 1 addition & 1 deletion base_layer/core/src/covenants/filters/fields_preserved.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ mod test {

#[test]
fn it_filters_outputs_that_match_input_fields() {
let covenant = covenant!(fields_preserved(@fields(@field::features_maturity, @field::features_flags)));
let covenant = covenant!(fields_preserved(@fields(@field::features_maturity, @field::features_output_type)));
let mut input = create_input();
input.set_maturity(42).unwrap();
input.features_mut().unwrap().sidechain_features = Some(Box::new(SideChainFeatures {}));
Expand Down
7 changes: 7 additions & 0 deletions base_layer/core/src/covenants/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ macro_rules! __covenant_inner {
$crate::__covenant_inner!(@ { $covenant } @covenant_lit($($inner)*),)
};

// @output_type(expr1), ...
(@ { $covenant:ident } @output_type($arg:expr $(,)?), $($tail:tt)*) => {
use $crate::transactions::transaction_components::OutputType::*;
$covenant.push_token($crate::covenants::CovenantToken::output_type($arg));
$crate::__covenant_inner!(@ { $covenant } $($tail)*)
};

// @arg(expr1, expr2, ...), ...
(@ { $covenant:ident } @$arg:ident($($args:expr),* $(,)?), $($tail:tt)*) => {
$covenant.push_token($crate::covenants::CovenantToken::$arg($($args),*));
Expand Down
42 changes: 25 additions & 17 deletions base_layer/core/src/covenants/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,27 @@ use std::{collections::VecDeque, io, iter::FromIterator};
use tari_common_types::types::{Commitment, FixedHash, PublicKey};
use tari_script::TariScript;

use crate::covenants::{
arguments::CovenantArg,
decoder::{CovenantDecodeError, CovenantReadExt},
fields::OutputField,
filters::{
AbsoluteHeightFilter,
AndFilter,
CovenantFilter,
FieldEqFilter,
FieldsHashedEqFilter,
FieldsPreservedFilter,
IdentityFilter,
NotFilter,
OrFilter,
OutputHashEqFilter,
XorFilter,
use crate::{
covenants::{
arguments::CovenantArg,
decoder::{CovenantDecodeError, CovenantReadExt},
fields::OutputField,
filters::{
AbsoluteHeightFilter,
AndFilter,
CovenantFilter,
FieldEqFilter,
FieldsHashedEqFilter,
FieldsPreservedFilter,
IdentityFilter,
NotFilter,
OrFilter,
OutputHashEqFilter,
XorFilter,
},
Covenant,
},
Covenant,
transactions::transaction_components::OutputType,
};

#[derive(Debug, Clone, PartialEq, Eq)]
Expand Down Expand Up @@ -174,6 +177,11 @@ impl CovenantToken {
CovenantArg::Uint(val).into()
}

#[allow(dead_code)]
pub fn output_type(output_type: OutputType) -> Self {
CovenantArg::OutputType(output_type).into()
}

#[allow(dead_code)]
pub fn field(field: OutputField) -> Self {
CovenantArg::OutputField(field).into()
Expand Down

0 comments on commit e21dfdb

Please sign in to comment.