diff --git a/base_layer/core/src/blocks/block.rs b/base_layer/core/src/blocks/block.rs index 30f606d07a..bae23b28b1 100644 --- a/base_layer/core/src/blocks/block.rs +++ b/base_layer/core/src/blocks/block.rs @@ -47,7 +47,7 @@ use crate::{ }, }; -#[derive(Clone, Debug, PartialEq, Error)] +#[derive(Clone, Debug, Error)] pub enum BlockValidationError { #[error("A transaction in the block failed to validate: `{0}`")] TransactionError(#[from] TransactionError), diff --git a/base_layer/core/src/blocks/genesis_block.rs b/base_layer/core/src/blocks/genesis_block.rs index 1882908c21..15a6e24de3 100644 --- a/base_layer/core/src/blocks/genesis_block.rs +++ b/base_layer/core/src/blocks/genesis_block.rs @@ -32,6 +32,7 @@ use tari_crypto::{ use crate::{ blocks::{block::Block, BlockHeader, BlockHeaderAccumulatedData, ChainBlock}, + covenants::Covenant, proof_of_work::{PowAlgorithm, ProofOfWork}, transactions::{ aggregated_body::AggregateBody, @@ -100,6 +101,7 @@ fn get_igor_genesis_block_raw() -> Block { sender_offset_public_key: Default::default(), // For genesis block: Metadata signature will never be checked metadata_signature: Default::default(), + covenant: Covenant::default(), }], vec![TransactionKernel { features: KernelFeatures::COINBASE_KERNEL, @@ -186,6 +188,7 @@ fn get_dibbler_genesis_block_raw() -> Block { sender_offset_public_key: Default::default(), // For genesis block: Metadata signature will never be checked metadata_signature: Default::default(), + covenant: Default::default() }], vec![TransactionKernel { features: KernelFeatures::COINBASE_KERNEL, diff --git a/base_layer/core/src/chain_storage/tests/blockchain_database.rs b/base_layer/core/src/chain_storage/tests/blockchain_database.rs index ab1d0c1ba2..d2a1a15dc2 100644 --- a/base_layer/core/src/chain_storage/tests/blockchain_database.rs +++ b/base_layer/core/src/chain_storage/tests/blockchain_database.rs @@ -405,6 +405,7 @@ mod add_block { lock_height: 0, features: Default::default(), script: tari_crypto::script![Nop], + covenant: Default::default(), input_data: None, }]); @@ -435,6 +436,7 @@ mod add_block { lock_height: 0, features: Default::default(), script: tari_crypto::script![Nop], + covenant: Default::default(), input_data: None, }]); diff --git a/base_layer/core/src/consensus/consensus_encoding.rs b/base_layer/core/src/consensus/consensus_encoding.rs index 26dd2b2b68..ae7720651e 100644 --- a/base_layer/core/src/consensus/consensus_encoding.rs +++ b/base_layer/core/src/consensus/consensus_encoding.rs @@ -101,7 +101,10 @@ mod impls { use std::io::Read; use tari_common_types::types::{Commitment, PrivateKey, PublicKey, Signature}; - use tari_crypto::script::TariScript; + use tari_crypto::{ + keys::{PublicKey as PublicKeyTrait, SecretKey as SecretKeyTrait}, + script::{ExecutionStack, TariScript}, + }; use tari_utilities::ByteArray; use super::*; @@ -125,15 +128,13 @@ mod impls { } } - //---------------------------------- PublicKey --------------------------------------------// - - impl ConsensusEncoding for PublicKey { + impl ConsensusEncoding for ExecutionStack { fn consensus_encode(&self, writer: &mut W) -> Result { - writer.write(self.as_bytes()) + self.as_bytes().consensus_encode(writer) } } - impl ConsensusEncodingSized for PublicKey { + impl ConsensusEncodingSized for ExecutionStack { fn consensus_encode_exact_size(&self) -> usize { let mut counter = ByteCounter::new(); // TODO: consensus_encode_exact_size must be cheap to run @@ -143,6 +144,20 @@ mod impls { } } + //---------------------------------- PublicKey --------------------------------------------// + + impl ConsensusEncoding for PublicKey { + fn consensus_encode(&self, writer: &mut W) -> Result { + writer.write(self.as_bytes()) + } + } + + impl ConsensusEncodingSized for PublicKey { + fn consensus_encode_exact_size(&self) -> usize { + PublicKey::key_length() + } + } + impl ConsensusDecoding for PublicKey { fn consensus_decode(reader: &mut R) -> Result { let mut buf = [0u8; 32]; @@ -152,6 +167,30 @@ mod impls { } } + //---------------------------------- PrivateKey --------------------------------------------// + + impl ConsensusEncoding for PrivateKey { + fn consensus_encode(&self, writer: &mut W) -> Result { + writer.write(self.as_bytes()) + } + } + + impl ConsensusEncodingSized for PrivateKey { + fn consensus_encode_exact_size(&self) -> usize { + PrivateKey::key_length() + } + } + + impl ConsensusDecoding for PrivateKey { + fn consensus_decode(reader: &mut R) -> Result { + let mut buf = [0u8; 32]; + reader.read_exact(&mut buf)?; + let sk = + PrivateKey::from_bytes(&buf[..]).map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?; + Ok(sk) + } + } + //---------------------------------- Commitment --------------------------------------------// impl ConsensusEncoding for Commitment { diff --git a/base_layer/core/src/covenants/covenant.rs b/base_layer/core/src/covenants/covenant.rs index c48607c8c3..d3fef485d2 100644 --- a/base_layer/core/src/covenants/covenant.rs +++ b/base_layer/core/src/covenants/covenant.rs @@ -48,9 +48,6 @@ impl Covenant { } pub fn from_bytes(mut bytes: &[u8]) -> Result { - if bytes.is_empty() { - return Ok(Self::new()); - } CovenantTokenDecoder::new(&mut bytes).collect() } @@ -131,7 +128,9 @@ impl FromIterator for Covenant { #[cfg(test)] mod test { + use super::*; use crate::{ + consensus::ToConsensusBytes, covenant, covenants::test::{create_input, create_outputs}, }; @@ -157,4 +156,29 @@ mod test { let num_matching_outputs = covenant.execute(0, &input, &outputs).unwrap(); assert_eq!(num_matching_outputs, 3); } + + mod consensus_encoding { + use super::*; + + #[test] + fn it_encodes_to_empty_bytes() { + let bytes = Covenant::new().to_consensus_bytes(); + assert_eq!(bytes.len(), 0); + } + } + + mod consensus_decoding { + use super::*; + + #[test] + fn it_is_identity_if_empty_bytes() { + let empty_buf = &[] as &[u8; 0]; + let covenant = Covenant::consensus_decode(&mut &empty_buf[..]).unwrap(); + + let outputs = create_outputs(10, Default::default()); + let input = create_input(); + let num_selected = covenant.execute(0, &input, &outputs).unwrap(); + assert_eq!(num_selected, 10); + } + } } diff --git a/base_layer/core/src/covenants/decoder.rs b/base_layer/core/src/covenants/decoder.rs index 3ff465796e..e2ea1aa099 100644 --- a/base_layer/core/src/covenants/decoder.rs +++ b/base_layer/core/src/covenants/decoder.rs @@ -131,6 +131,12 @@ mod test { covenants::{arguments::CovenantArg, fields::OutputField, filters::CovenantFilter}, }; + #[test] + fn it_immediately_ends_iterator_given_empty_bytes() { + let buf = &[] as &[u8; 0]; + assert!(CovenantTokenDecoder::new(&mut &buf[..]).next().is_none()); + } + #[test] fn it_decodes_from_well_formed_bytes() { let hash = from_hex("53563b674ba8e5166adb57afa8355bcf2ee759941eef8f8959b802367c2558bd").unwrap(); diff --git a/base_layer/core/src/covenants/fields.rs b/base_layer/core/src/covenants/fields.rs index 49f19bc06b..827401f75c 100644 --- a/base_layer/core/src/covenants/fields.rs +++ b/base_layer/core/src/covenants/fields.rs @@ -87,7 +87,7 @@ impl OutputField { Commitment => &output.commitment as &dyn Any, Script => &output.script as &dyn Any, SenderOffsetPublicKey => &output.sender_offset_public_key as &dyn Any, - Covenant => unimplemented!(), + Covenant => &output.covenant as &dyn Any, Features => &output.features as &dyn Any, FeaturesFlags => &output.features.flags as &dyn Any, FeaturesMaturity => &output.features.maturity as &dyn Any, @@ -104,7 +104,7 @@ impl OutputField { Commitment => output.commitment.to_consensus_bytes(), Script => output.script.to_consensus_bytes(), SenderOffsetPublicKey => output.sender_offset_public_key.to_consensus_bytes(), - Covenant => unimplemented!(), + Covenant => output.covenant.to_consensus_bytes(), Features => output.features.to_consensus_bytes(), FeaturesFlags => output.features.flags.to_consensus_bytes(), FeaturesMaturity => output.features.maturity.to_consensus_bytes(), @@ -130,7 +130,7 @@ impl OutputField { Commitment => input.commitment == output.commitment, Script => input.script == output.script, SenderOffsetPublicKey => input.sender_offset_public_key == output.sender_offset_public_key, - Covenant => unimplemented!(), + Covenant => input.covenant == output.covenant, Features => input.features == output.features, FeaturesFlags => input.features.flags == output.features.flags, FeaturesMaturity => input.features.maturity == output.features.maturity, @@ -164,52 +164,52 @@ impl OutputField { //---------------------------------- Macro helpers --------------------------------------------// #[allow(dead_code)] - pub(super) fn commitment() -> Self { + pub fn commitment() -> Self { OutputField::Commitment } #[allow(dead_code)] - pub(super) fn script() -> Self { + pub fn script() -> Self { OutputField::Script } #[allow(dead_code)] - pub(super) fn sender_offset_public_key() -> Self { + pub fn sender_offset_public_key() -> Self { OutputField::SenderOffsetPublicKey } #[allow(dead_code)] - pub(super) fn covenant() -> Self { + pub fn covenant() -> Self { OutputField::Covenant } #[allow(dead_code)] - pub(super) fn features() -> Self { + pub fn features() -> Self { OutputField::Features } #[allow(dead_code)] - pub(super) fn features_flags() -> Self { + pub fn features_flags() -> Self { OutputField::FeaturesFlags } #[allow(dead_code)] - pub(super) fn features_maturity() -> Self { + pub fn features_maturity() -> Self { OutputField::FeaturesMaturity } #[allow(dead_code)] - pub(super) fn features_unique_id() -> Self { + pub fn features_unique_id() -> Self { OutputField::FeaturesUniqueId } #[allow(dead_code)] - pub(super) fn features_parent_public_key() -> Self { + pub fn features_parent_public_key() -> Self { OutputField::FeaturesParentPublicKey } #[allow(dead_code)] - pub(super) fn features_metadata() -> Self { + pub fn features_metadata() -> Self { OutputField::FeaturesMetadata } } @@ -311,19 +311,22 @@ impl FromIterator for OutputFields { #[cfg(test)] mod test { use super::*; - use crate::transactions::transaction::OutputFeatures; + use crate::{ + covenants::test::create_outputs, + transactions::{test_helpers::UtxoTestParams, transaction::OutputFeatures}, + }; #[test] fn get_field_value_ref() { - let output = TransactionOutput::new( - Default::default(), - Default::default(), - Default::default(), - Default::default(), - Default::default(), - Default::default(), - ); + let mut features = OutputFeatures::default(); + features.maturity = 42; + let output = create_outputs(1, UtxoTestParams { + output_features: features.clone(), + ..Default::default() + }) + .pop() + .unwrap(); let r = OutputField::Features.get_field_value_ref::(&output); - assert_eq!(*r.unwrap(), OutputFeatures::default()); + assert_eq!(*r.unwrap(), features); } } diff --git a/base_layer/core/src/covenants/mod.rs b/base_layer/core/src/covenants/mod.rs index fff53dbfb2..ef7acf40f3 100644 --- a/base_layer/core/src/covenants/mod.rs +++ b/base_layer/core/src/covenants/mod.rs @@ -36,14 +36,16 @@ mod error; mod fields; mod filters; mod output_set; +mod serde; mod token; pub use covenant::Covenant; +pub use error::CovenantError; // Used in macro #[allow(unused_imports)] -pub(self) use fields::OutputField; +pub(crate) use fields::OutputField; #[allow(unused_imports)] -pub(self) use token::CovenantToken; +pub(crate) use token::CovenantToken; #[macro_use] mod macros; diff --git a/base_layer/core/src/covenants/serde.rs b/base_layer/core/src/covenants/serde.rs new file mode 100644 index 0000000000..bda6dce813 --- /dev/null +++ b/base_layer/core/src/covenants/serde.rs @@ -0,0 +1,91 @@ +// Copyright 2021, 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 std::fmt; + +use serde::{ + de::{Error, Visitor}, + Deserialize, + Deserializer, + Serialize, + Serializer, +}; +use tari_utilities::hex::{from_hex, Hex}; + +use crate::{ + consensus::{ConsensusDecoding, ToConsensusBytes}, + covenants::Covenant, +}; + +impl Serialize for Covenant { + fn serialize(&self, ser: S) -> Result + where S: Serializer { + let bytes = self.to_consensus_bytes(); + if ser.is_human_readable() { + ser.serialize_str(&bytes.to_hex()) + } else { + ser.serialize_bytes(&bytes) + } + } +} + +struct CovenantVisitor; + +impl<'de> Visitor<'de> for CovenantVisitor { + type Value = Covenant; + + fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.write_str("Expecting a binary array or hex string") + } + + fn visit_str(self, v: &str) -> Result + where E: Error { + let bytes = from_hex(v).map_err(|e| E::custom(e.to_string()))?; + self.visit_bytes(&bytes) + } + + fn visit_string(self, v: String) -> Result + where E: Error { + self.visit_str(&v) + } + + fn visit_bytes(self, mut v: &[u8]) -> Result + where E: Error { + Covenant::consensus_decode(&mut v).map_err(|e| E::custom(e.to_string())) + } + + fn visit_borrowed_bytes(self, v: &'de [u8]) -> Result + where E: Error { + self.visit_bytes(v) + } +} + +impl<'de> Deserialize<'de> for Covenant { + fn deserialize(de: D) -> Result + where D: Deserializer<'de> { + if de.is_human_readable() { + de.deserialize_string(CovenantVisitor) + } else { + de.deserialize_bytes(CovenantVisitor) + } + } +} diff --git a/base_layer/core/src/covenants/token.rs b/base_layer/core/src/covenants/token.rs index d93e12e2d5..86bc5a30da 100644 --- a/base_layer/core/src/covenants/token.rs +++ b/base_layer/core/src/covenants/token.rs @@ -95,97 +95,97 @@ impl CovenantToken { //---------------------------------- Macro helper functions --------------------------------------------// #[allow(dead_code)] - pub(super) fn identity() -> Self { + pub fn identity() -> Self { CovenantToken::Filter(CovenantFilter::Identity(IdentityFilter)) } #[allow(dead_code)] - pub(super) fn and() -> Self { + pub fn and() -> Self { CovenantToken::Filter(CovenantFilter::And(AndFilter)) } #[allow(dead_code)] - pub(super) fn or() -> Self { + pub fn or() -> Self { CovenantToken::Filter(CovenantFilter::Or(OrFilter)) } #[allow(dead_code)] - pub(super) fn xor() -> Self { + pub fn xor() -> Self { CovenantToken::Filter(CovenantFilter::Xor(XorFilter)) } #[allow(dead_code)] - pub(super) fn not() -> Self { + pub fn not() -> Self { CovenantToken::Filter(CovenantFilter::Not(NotFilter)) } #[allow(dead_code)] - pub(super) fn output_hash_eq() -> Self { + pub fn output_hash_eq() -> Self { CovenantToken::Filter(CovenantFilter::OutputHashEq(OutputHashEqFilter)) } #[allow(dead_code)] - pub(super) fn fields_preserved() -> Self { + pub fn fields_preserved() -> Self { CovenantToken::Filter(CovenantFilter::FieldsPreserved(FieldsPreservedFilter)) } #[allow(dead_code)] - pub(super) fn field_eq() -> Self { + pub fn field_eq() -> Self { CovenantToken::Filter(CovenantFilter::FieldEq(FieldEqFilter)) } #[allow(dead_code)] - pub(super) fn fields_hashed_eq() -> Self { + pub fn fields_hashed_eq() -> Self { CovenantToken::Filter(CovenantFilter::FieldsHashedEq(FieldsHashedEqFilter)) } #[allow(dead_code)] - pub(super) fn absolute_height() -> Self { + pub fn absolute_height() -> Self { CovenantToken::Filter(CovenantFilter::AbsoluteHeight(AbsoluteHeightFilter)) } #[allow(dead_code)] - pub(super) fn hash(hash: Hash) -> Self { + pub fn hash(hash: Hash) -> Self { CovenantToken::Arg(CovenantArg::Hash(hash)) } #[allow(dead_code)] - pub(super) fn public_key(public_key: PublicKey) -> Self { + pub fn public_key(public_key: PublicKey) -> Self { CovenantToken::Arg(CovenantArg::PublicKey(public_key)) } #[allow(dead_code)] - pub(super) fn commitment(commitment: Commitment) -> Self { + pub fn commitment(commitment: Commitment) -> Self { CovenantToken::Arg(CovenantArg::Commitment(commitment)) } #[allow(dead_code)] - pub(super) fn script(script: TariScript) -> Self { + pub fn script(script: TariScript) -> Self { CovenantToken::Arg(CovenantArg::TariScript(script)) } #[allow(dead_code)] - pub(super) fn covenant(covenant: Covenant) -> Self { + pub fn covenant(covenant: Covenant) -> Self { CovenantToken::Arg(CovenantArg::Covenant(covenant)) } #[allow(dead_code)] - pub(super) fn uint(val: u64) -> Self { + pub fn uint(val: u64) -> Self { CovenantToken::Arg(CovenantArg::Uint(val)) } #[allow(dead_code)] - pub(super) fn field(field: OutputField) -> Self { + pub fn field(field: OutputField) -> Self { CovenantToken::Arg(CovenantArg::OutputField(field)) } #[allow(dead_code)] - pub(super) fn fields(fields: Vec) -> Self { + pub fn fields(fields: Vec) -> Self { CovenantToken::Arg(CovenantArg::OutputFields(fields.into())) } #[allow(dead_code)] - pub(super) fn bytes(bytes: Vec) -> Self { + pub fn bytes(bytes: Vec) -> Self { CovenantToken::Arg(CovenantArg::Bytes(bytes)) } } diff --git a/base_layer/core/src/mempool/unconfirmed_pool/unconfirmed_pool.rs b/base_layer/core/src/mempool/unconfirmed_pool/unconfirmed_pool.rs index ef2a1f6f38..cb7980fcb2 100644 --- a/base_layer/core/src/mempool/unconfirmed_pool/unconfirmed_pool.rs +++ b/base_layer/core/src/mempool/unconfirmed_pool/unconfirmed_pool.rs @@ -599,11 +599,9 @@ mod test { .unwrap(); let factories = CryptoFactories::default(); - let mut stx_protocol = stx_builder - .build::(&factories, None, Some(u64::MAX)) - .unwrap(); + let mut stx_protocol = stx_builder.build::(&factories, None, u64::MAX).unwrap(); stx_protocol - .finalize(KernelFeatures::empty(), &factories, None, Some(u64::MAX)) + .finalize(KernelFeatures::empty(), &factories, None, u64::MAX) .unwrap(); let tx3 = stx_protocol.get_transaction().unwrap().clone(); diff --git a/base_layer/core/src/proto/transaction.proto b/base_layer/core/src/proto/transaction.proto index 174fee741e..4b9410c2f4 100644 --- a/base_layer/core/src/proto/transaction.proto +++ b/base_layer/core/src/proto/transaction.proto @@ -42,6 +42,8 @@ message TransactionInput { ComSignature script_signature = 6; // The offset pubkey, K_O bytes sender_offset_public_key = 7; + // The serialised covenant + bytes covenant = 8; } // Output for a transaction, defining the new ownership of coins that are being transferred. The commitment is a @@ -60,8 +62,8 @@ message TransactionOutput { bytes sender_offset_public_key = 5; // UTXO signature with the script offset private key, k_O ComSignature metadata_signature = 6; - // Numbering 7-8 taken - + // The serialised covenant + bytes covenant = 7; } // Options for UTXO's diff --git a/base_layer/core/src/proto/transaction.rs b/base_layer/core/src/proto/transaction.rs index 4c748ae9e3..8641a0de66 100644 --- a/base_layer/core/src/proto/transaction.rs +++ b/base_layer/core/src/proto/transaction.rs @@ -32,6 +32,8 @@ use tari_crypto::{ use tari_utilities::convert::try_convert_all; use crate::{ + consensus::{ConsensusDecoding, ToConsensusBytes}, + covenants::Covenant, proto, transactions::{ aggregated_body::AggregateBody, @@ -128,6 +130,7 @@ impl TryFrom for TransactionInput { input_data: ExecutionStack::from_bytes(input.input_data.as_slice()).map_err(|err| format!("{:?}", err))?, script_signature, sender_offset_public_key, + covenant: Covenant::consensus_decode(&mut input.covenant.as_slice()).map_err(|err| err.to_string())?, }) } } @@ -141,6 +144,7 @@ impl From for proto::types::TransactionInput { input_data: input.input_data.as_bytes(), script_signature: Some(input.script_signature.into()), sender_offset_public_key: input.sender_offset_public_key.as_bytes().to_vec(), + covenant: input.covenant.to_consensus_bytes(), } } } @@ -173,6 +177,8 @@ impl TryFrom for TransactionOutput { .try_into() .map_err(|_| "Metadata signature could not be converted".to_string())?; + let covenant = Covenant::consensus_decode(&mut output.covenant.as_slice()).map_err(|err| err.to_string())?; + Ok(Self { features, commitment, @@ -180,6 +186,7 @@ impl TryFrom for TransactionOutput { script, sender_offset_public_key, metadata_signature, + covenant, }) } } @@ -193,6 +200,7 @@ impl From for proto::types::TransactionOutput { script: output.script.as_bytes(), sender_offset_public_key: output.sender_offset_public_key.as_bytes().to_vec(), metadata_signature: Some(output.metadata_signature.into()), + covenant: output.covenant.to_consensus_bytes(), } } } diff --git a/base_layer/core/src/transactions/aggregated_body.rs b/base_layer/core/src/transactions/aggregated_body.rs index 5c437e16c7..31a06205ab 100644 --- a/base_layer/core/src/transactions/aggregated_body.rs +++ b/base_layer/core/src/transactions/aggregated_body.rs @@ -356,7 +356,7 @@ impl AggregateBody { total_reward: MicroTari, factories: &CryptoFactories, prev_header: Option, - height: Option, + height: u64, ) -> Result<(), TransactionError> { self.verify_kernel_signatures()?; @@ -369,7 +369,9 @@ impl AggregateBody { self.verify_metadata_signatures()?; let script_offset_g = PublicKey::from_secret_key(script_offset); - self.validate_script_offset(script_offset_g, &factories.commitment, prev_header, height) + self.validate_script_offset(script_offset_g, &factories.commitment, prev_header, height)?; + self.validate_covenants(height)?; + Ok(()) } pub fn dissolve(self) -> (Vec, Vec, Vec) { @@ -434,13 +436,12 @@ impl AggregateBody { script_offset: PublicKey, factory: &CommitmentFactory, prev_header: Option, - height: Option, + height: u64, ) -> Result<(), TransactionError> { trace!(target: LOG_TARGET, "Checking script offset"); // lets count up the input script public keys let mut input_keys = PublicKey::default(); let prev_hash: [u8; 32] = prev_header.unwrap_or_default().as_slice().try_into().unwrap_or([0; 32]); - let height = height.unwrap_or_default(); for input in &self.inputs { let context = ScriptContext::new(height, &prev_hash, &input.commitment); input_keys = input_keys + input.run_and_verify_script(factory, Some(context))?; @@ -461,6 +462,13 @@ impl AggregateBody { Ok(()) } + fn validate_covenants(&self, height: u64) -> Result<(), TransactionError> { + for input in self.inputs.iter() { + input.covenant.execute(height, input, &self.outputs)?; + } + Ok(()) + } + fn validate_range_proofs(&self, range_proof_service: &RangeProofService) -> Result<(), TransactionError> { trace!(target: LOG_TARGET, "Checking range proofs"); for o in &self.outputs { diff --git a/base_layer/core/src/transactions/coinbase_builder.rs b/base_layer/core/src/transactions/coinbase_builder.rs index 59c5fd30cc..d91b43a294 100644 --- a/base_layer/core/src/transactions/coinbase_builder.rs +++ b/base_layer/core/src/transactions/coinbase_builder.rs @@ -37,6 +37,7 @@ use crate::{ emission::{Emission, EmissionSchedule}, ConsensusConstants, }, + covenants::Covenant, transactions::{ crypto_factories::CryptoFactories, tari_amount::{uT, MicroTari}, @@ -80,6 +81,7 @@ pub struct CoinbaseBuilder { script: Option, private_nonce: Option, rewind_data: Option, + covenant: Covenant, } impl CoinbaseBuilder { @@ -95,6 +97,7 @@ impl CoinbaseBuilder { script: None, private_nonce: None, rewind_data: None, + covenant: Covenant::default(), } } @@ -129,6 +132,12 @@ impl CoinbaseBuilder { self } + /// Set the covenant for this transaction. + pub fn with_covenant(mut self, covenant: Covenant) -> Self { + self.covenant = covenant; + self + } + /// The nonce to be used for this transaction. This will usually be provided by a miner's wallet instance. pub fn with_nonce(mut self, nonce: PrivateKey) -> Self { self.private_nonce = Some(nonce); @@ -189,6 +198,7 @@ impl CoinbaseBuilder { let sender_offset_private_key = PrivateKey::random(&mut OsRng); let sender_offset_public_key = PublicKey::from_secret_key(&sender_offset_private_key); + let covenant = self.covenant; let metadata_sig = TransactionOutput::create_final_metadata_signature( &total_reward, @@ -196,6 +206,7 @@ impl CoinbaseBuilder { &script, &output_features, &sender_offset_private_key, + &covenant, ) .map_err(|e| CoinbaseBuildError::BuildError(e.to_string()))?; @@ -209,6 +220,7 @@ impl CoinbaseBuilder { sender_offset_public_key, metadata_sig, 0, + covenant, ); // TODO: Verify bullet proof? let output = if let Some(rewind_data) = self.rewind_data.as_ref() { @@ -237,7 +249,7 @@ impl CoinbaseBuilder { .with_reward(total_reward) .with_kernel(kernel); let tx = builder - .build(&self.factories, None, Some(height)) + .build(&self.factories, None, height) .map_err(|e| CoinbaseBuildError::BuildError(e.to_string()))?; Ok((tx, unblinded_output)) } @@ -328,15 +340,14 @@ mod test { .open_value(&p.spend_key, block_reward.into(), utxo.commitment())); utxo.verify_range_proof(&factories.range_proof).unwrap(); assert!(utxo.features.flags.contains(OutputFlags::COINBASE_OUTPUT)); - assert_eq!( - tx.body.check_coinbase_output( + tx.body + .check_coinbase_output( block_reward, rules.consensus_constants(0).coinbase_lock_height(), &factories, - 42 - ), - Ok(()) - ); + 42, + ) + .unwrap(); } #[test] @@ -386,7 +397,7 @@ mod test { .build(rules.consensus_constants(42), rules.emission_schedule()) .unwrap(); tx.body.outputs_mut()[0].features.maturity = 1; - assert_eq!( + assert!(matches!( tx.body.check_coinbase_output( block_reward, rules.consensus_constants(0).coinbase_lock_height(), @@ -394,7 +405,7 @@ mod test { 42 ), Err(TransactionError::InvalidCoinbaseMaturity) - ); + )); } #[test] @@ -430,7 +441,7 @@ mod test { tx.body.add_kernel(coinbase_kernel2); // test catches that coinbase amount is wrong - assert_eq!( + assert!(matches!( tx.body.check_coinbase_output( block_reward, rules.consensus_constants(0).coinbase_lock_height(), @@ -438,7 +449,7 @@ mod test { 42 ), Err(TransactionError::InvalidCoinbase) - ); + )); // lets construct a correct one now, with the correct amount. let builder = CoinbaseBuilder::new(factories.clone()); let builder = builder @@ -502,7 +513,7 @@ mod test { tx_kernel_test.body.add_kernel(coinbase_kernel2); // test catches that coinbase count on the utxo is wrong - assert_eq!( + assert!(matches!( tx.body.check_coinbase_output( block_reward, rules.consensus_constants(0).coinbase_lock_height(), @@ -510,9 +521,9 @@ mod test { 42 ), Err(TransactionError::MoreThanOneCoinbase) - ); + )); // test catches that coinbase count on the kernel is wrong - assert_eq!( + assert!(matches!( tx_kernel_test.body.check_coinbase_output( block_reward, rules.consensus_constants(0).coinbase_lock_height(), @@ -520,19 +531,18 @@ mod test { 42 ), Err(TransactionError::MoreThanOneCoinbase) - ); + )); // testing that "block" is still valid - assert_eq!( - tx.body.validate_internal_consistency( + tx.body + .validate_internal_consistency( &BlindingFactor::default(), &PrivateKey::default(), false, block_reward, &factories, None, - Some(u64::MAX) - ), - Ok(()) - ); + u64::MAX, + ) + .unwrap(); } } diff --git a/base_layer/core/src/transactions/test_helpers.rs b/base_layer/core/src/transactions/test_helpers.rs index d80c8b472a..cc47076525 100644 --- a/base_layer/core/src/transactions/test_helpers.rs +++ b/base_layer/core/src/transactions/test_helpers.rs @@ -37,6 +37,7 @@ use tari_crypto::{ use crate::{ consensus::{ConsensusEncodingSized, ConsensusManager}, + covenants::Covenant, transactions::{ crypto_factories::CryptoFactories, fee::Fee, @@ -95,6 +96,7 @@ pub struct UtxoTestParams { pub script: TariScript, pub output_features: OutputFeatures, pub input_data: Option, + pub covenant: Covenant, } impl Default for UtxoTestParams { @@ -104,6 +106,7 @@ impl Default for UtxoTestParams { script: script![Nop], output_features: Default::default(), input_data: None, + covenant: Covenant::default(), } } } @@ -143,6 +146,7 @@ impl TestParams { ¶ms.script, ¶ms.output_features, &self.sender_offset_private_key, + ¶ms.covenant, ) .unwrap(); @@ -158,6 +162,7 @@ impl TestParams { self.sender_offset_public_key.clone(), metadata_signature, 0, + params.covenant, ) } @@ -287,6 +292,7 @@ macro_rules! txn_schema { lock_height: $lock, features: $features.clone(), script: tari_crypto::script![Nop], + covenant: Default::default(), input_data: None, } }}; @@ -341,6 +347,7 @@ pub struct TransactionSchema { pub features: OutputFeatures, pub script: TariScript, pub input_data: Option, + pub covenant: Covenant, } fn default_metadata_byte_size() -> usize { @@ -454,9 +461,9 @@ pub fn create_transaction_with( stx_builder.with_output(utxo, script_offset_pvt_key).unwrap(); }); - let mut stx_protocol = stx_builder.build::(&factories, None, Some(u64::MAX)).unwrap(); + let mut stx_protocol = stx_builder.build::(&factories, None, u64::MAX).unwrap(); stx_protocol - .finalize(KernelFeatures::empty(), &factories, None, Some(u64::MAX)) + .finalize(KernelFeatures::empty(), &factories, None, u64::MAX) .unwrap(); stx_protocol.take_transaction().unwrap() } @@ -502,6 +509,8 @@ pub fn spend_utxos(schema: TransactionSchema) -> (Transaction, Vec (Transaction, Vec (Transaction, Vec(&factories, None, Some(u64::MAX)).unwrap(); + let mut stx_protocol = stx_builder.build::(&factories, None, u64::MAX).unwrap(); let change = stx_protocol.get_change_amount().unwrap(); // The change output is assigned its own random script offset private key let change_sender_offset_public_key = stx_protocol.get_change_sender_offset_public_key().unwrap().unwrap(); let script = script!(Nop); + let covenant = Covenant::default(); let metadata_sig = TransactionOutput::create_final_metadata_signature( &change, &test_params_change_and_txn.change_spend_key, &script, &schema.features, &test_params_change_and_txn.sender_offset_private_key, + &covenant, ) .unwrap(); @@ -552,10 +564,11 @@ pub fn spend_utxos(schema: TransactionSchema) -> (Transaction, Vec (TransactionOutput, PrivateKey, PrivateKey) { let keys = generate_keys(); let offset_keys = generate_keys(); let commitment = factories.commitment.commit_value(&keys.k, value.into()); let proof = factories.range_proof.construct_proof(&keys.k, value.into()).unwrap(); - let metadata_sig = - TransactionOutput::create_final_metadata_signature(&value, &keys.k, script, &features, &offset_keys.k).unwrap(); + let metadata_sig = TransactionOutput::create_final_metadata_signature( + &value, + &keys.k, + script, + &features, + &offset_keys.k, + &covenant, + ) + .unwrap(); let utxo = TransactionOutput::new( features, @@ -594,6 +615,7 @@ pub fn create_utxo( script.clone(), offset_keys.pk, metadata_sig, + covenant.clone(), ); (utxo, keys.k, offset_keys.k) } diff --git a/base_layer/core/src/transactions/transaction/error.rs b/base_layer/core/src/transactions/transaction/error.rs index bbe8e18ffa..dbb92a7a0a 100644 --- a/base_layer/core/src/transactions/transaction/error.rs +++ b/base_layer/core/src/transactions/transaction/error.rs @@ -27,6 +27,8 @@ use serde::{Deserialize, Serialize}; use tari_crypto::{range_proof::RangeProofError, script::ScriptError, signatures::CommitmentSignatureError}; use thiserror::Error; +use crate::covenants::CovenantError; + //---------------------------------------- TransactionError ----------------------------------------------------// #[derive(Clone, Debug, PartialEq, Error, Deserialize, Serialize)] pub enum TransactionError { @@ -60,4 +62,12 @@ pub enum TransactionError { ScriptOffset, #[error("Error executing script: {0}")] ScriptExecutionError(String), + #[error("Error executing covenant: {0}")] + CovenantError(String), +} + +impl From for TransactionError { + fn from(err: CovenantError) -> Self { + TransactionError::CovenantError(err.to_string()) + } } diff --git a/base_layer/core/src/transactions/transaction/mod.rs b/base_layer/core/src/transactions/transaction/mod.rs index 58bf2f53ab..8d61ef4d7e 100644 --- a/base_layer/core/src/transactions/transaction/mod.rs +++ b/base_layer/core/src/transactions/transaction/mod.rs @@ -46,6 +46,8 @@ pub use transaction_output::TransactionOutput; pub use unblinded_output::UnblindedOutput; pub use unblinded_output_builder::UnblindedOutputBuilder; +use crate::{consensus::ToConsensusBytes, covenants::Covenant}; + mod asset_output_features; mod error; mod full_rewind_result; @@ -82,13 +84,19 @@ pub const MAX_TRANSACTION_RECIPIENTS: usize = 15; /// a) It is a significant performance boost, since the RP is the biggest part of an output /// b) Range proofs are committed to elsewhere and so we'd be hashing them twice (and as mentioned, this is slow) /// c) TransactionInputs will now have the same hash as UTXOs, which makes locating STXOs easier when doing reorgs -pub fn hash_output(features: &OutputFeatures, commitment: &Commitment, script: &TariScript) -> Vec { +pub(super) fn hash_output( + features: &OutputFeatures, + commitment: &Commitment, + script: &TariScript, + covenant: &Covenant, +) -> Vec { HashDigest::new() // TODO: use consensus encoding #testnet_reset .chain(features.to_v1_bytes()) .chain(commitment.as_bytes()) // .chain(range proof) // See docs as to why we exclude this .chain(script.as_bytes()) + .chain(covenant.to_consensus_bytes()) .finalize() .to_vec() } @@ -108,6 +116,7 @@ mod test { script::{ExecutionStack, StackItem}, tari_utilities::{hex::Hex, Hashable}, }; + use tari_test_utils::unpack_enum; use super::*; use crate::{ @@ -178,12 +187,10 @@ mod test { let tx_output2 = unblinded_output2.as_transaction_output(&factories); match tx_output2 { Ok(_) => panic!("Range proof should have failed to verify"), - Err(e) => assert_eq!( - e, - TransactionError::ValidationError( - "Value provided is outside the range allowed by the range proof".to_string() - ) - ), + Err(e) => { + unpack_enum!(TransactionError::ValidationError(s) = e); + assert_eq!(s, "Value provided is outside the range allowed by the range proof"); + }, } let value = 2u64.pow(32) + 1; @@ -206,8 +213,10 @@ mod test { &script, &output_features, &test_params_2.sender_offset_private_key, + &Covenant::default(), ) .unwrap(), + Covenant::default(), ); tx_output3.verify_range_proof(&factories.range_proof).unwrap_err(); } @@ -287,6 +296,7 @@ mod test { OutputFeatures::default(), c, script, + Covenant::default(), input_data, script_signature, offset_pub_key, @@ -300,8 +310,11 @@ mod test { kernel.lock_height = 2; tx.body.add_input(input.clone()); tx.body.add_kernel(kernel.clone()); - assert_eq!(tx.body.check_stxo_rules(1), Err(TransactionError::InputMaturity)); - assert_eq!(tx.body.check_stxo_rules(5), Ok(())); + assert!(matches!( + tx.body.check_stxo_rules(1), + Err(TransactionError::InputMaturity) + )); + tx.body.check_stxo_rules(5).unwrap(); assert_eq!(tx.max_input_maturity(), 5); assert_eq!(tx.max_kernel_timelock(), 2); @@ -332,7 +345,7 @@ mod test { let factories = CryptoFactories::default(); assert!(tx - .validate_internal_consistency(false, &factories, None, None, None) + .validate_internal_consistency(false, &factories, None, None, u64::MAX) .is_ok()); } @@ -347,7 +360,7 @@ mod test { let factories = CryptoFactories::default(); assert!(tx - .validate_internal_consistency(false, &factories, None, None, None) + .validate_internal_consistency(false, &factories, None, None, u64::MAX) .is_ok()); let schema = txn_schema!(from: vec![outputs[1].clone()], to: vec![1 * T, 2 * T]); @@ -380,12 +393,12 @@ mod test { // Validate basis transaction where cut-through has not been applied. assert!(tx3 - .validate_internal_consistency(false, &factories, None, None, Some(u64::MAX)) + .validate_internal_consistency(false, &factories, None, None, u64::MAX) .is_ok()); // tx3_cut_through has manual cut-through, it should not be possible so this should fail assert!(tx3_cut_through - .validate_internal_consistency(false, &factories, None, None, Some(u64::MAX)) + .validate_internal_consistency(false, &factories, None, None, u64::MAX) .is_err()); } @@ -424,7 +437,7 @@ mod test { let factories = CryptoFactories::default(); let err = tx - .validate_internal_consistency(false, &factories, None, None, Some(u64::MAX)) + .validate_internal_consistency(false, &factories, None, None, u64::MAX) .unwrap_err(); assert!(matches!(err, TransactionError::InvalidSignatureError(_))); } @@ -456,18 +469,18 @@ mod test { .as_rewindable_transaction_output(&factories, &rewind_data) .unwrap(); - assert_eq!( + assert!(matches!( output.rewind_range_proof_value_only( &factories.range_proof, &public_random_key, &rewind_blinding_public_key ), Err(TransactionError::RangeProofError(RangeProofError::InvalidRewind)) - ); - assert_eq!( + )); + assert!(matches!( output.rewind_range_proof_value_only(&factories.range_proof, &rewind_public_key, &public_random_key), Err(TransactionError::RangeProofError(RangeProofError::InvalidRewind)) - ); + )); let rewind_result = output .rewind_range_proof_value_only(&factories.range_proof, &rewind_public_key, &rewind_blinding_public_key) @@ -476,14 +489,14 @@ mod test { assert_eq!(rewind_result.committed_value, v); assert_eq!(&rewind_result.proof_message, proof_message); - assert_eq!( + assert!(matches!( output.full_rewind_range_proof(&factories.range_proof, &random_key, &rewind_blinding_key), Err(TransactionError::RangeProofError(RangeProofError::InvalidRewind)) - ); - assert_eq!( + )); + assert!(matches!( output.full_rewind_range_proof(&factories.range_proof, &rewind_key, &random_key), Err(TransactionError::RangeProofError(RangeProofError::InvalidRewind)) - ); + )); let full_rewind_result = output .full_rewind_range_proof(&factories.range_proof, &rewind_key, &rewind_blinding_key) diff --git a/base_layer/core/src/transactions/transaction/transaction.rs b/base_layer/core/src/transactions/transaction/transaction.rs index 366602c107..75833a8068 100644 --- a/base_layer/core/src/transactions/transaction/transaction.rs +++ b/base_layer/core/src/transactions/transaction/transaction.rs @@ -87,7 +87,7 @@ impl Transaction { factories: &CryptoFactories, reward: Option, prev_header: Option, - height: Option, + height: u64, ) -> Result<(), TransactionError> { let reward = reward.unwrap_or_else(|| 0 * uT); self.body.validate_internal_consistency( diff --git a/base_layer/core/src/transactions/transaction/transaction_builder.rs b/base_layer/core/src/transactions/transaction/transaction_builder.rs index 23a775cc39..f334ef2934 100644 --- a/base_layer/core/src/transactions/transaction/transaction_builder.rs +++ b/base_layer/core/src/transactions/transaction/transaction_builder.rs @@ -98,7 +98,7 @@ impl TransactionBuilder { self, factories: &CryptoFactories, prev_header: Option, - height: Option, + height: u64, ) -> Result { if let (Some(script_offset), Some(offset)) = (self.script_offset, self.offset) { let (i, o, k) = self.body.dissolve(); diff --git a/base_layer/core/src/transactions/transaction/transaction_input.rs b/base_layer/core/src/transactions/transaction/transaction_input.rs index 96a996496c..d7c8a75f09 100644 --- a/base_layer/core/src/transactions/transaction/transaction_input.rs +++ b/base_layer/core/src/transactions/transaction/transaction_input.rs @@ -37,9 +37,13 @@ use tari_crypto::{ tari_utilities::{hex::Hex, ByteArray, Hashable}, }; -use crate::transactions::{ - transaction, - transaction::{transaction_output::TransactionOutput, OutputFeatures, TransactionError, UnblindedOutput}, +use crate::{ + consensus::ToConsensusBytes, + covenants::Covenant, + transactions::{ + transaction, + transaction::{transaction_output::TransactionOutput, OutputFeatures, TransactionError, UnblindedOutput}, + }, }; /// A transaction input. @@ -51,10 +55,12 @@ pub struct TransactionInput { pub features: OutputFeatures, /// The commitment referencing the output being spent. pub commitment: Commitment, - /// The serialised script + /// The transaction script pub script: TariScript, /// The script input data, if any pub input_data: ExecutionStack, + /// The transaction covenant + pub covenant: Covenant, /// A signature with k_s, signing the script, input data, and mined height pub script_signature: ComSignature, /// The offset public key, K_O @@ -68,21 +74,23 @@ impl TransactionInput { features: OutputFeatures, commitment: Commitment, script: TariScript, + covenant: Covenant, input_data: ExecutionStack, script_signature: ComSignature, sender_offset_public_key: PublicKey, - ) -> TransactionInput { - TransactionInput { + ) -> Self { + Self { features, commitment, script, + covenant, input_data, script_signature, sender_offset_public_key, } } - pub fn build_script_challenge( + pub(super) fn build_script_challenge( nonce_commitment: &Commitment, script: &TariScript, input_data: &ExecutionStack, @@ -171,7 +179,7 @@ impl TransactionInput { /// Returns the hash of the output data contained in this input. /// This hash matches the hash of a transaction output that this input spends. pub fn output_hash(&self) -> Vec { - transaction::hash_output(&self.features, &self.commitment, &self.script) + transaction::hash_output(&self.features, &self.commitment, &self.script, &self.covenant) } } @@ -179,14 +187,15 @@ impl TransactionInput { impl Hashable for TransactionInput { fn hash(&self) -> Vec { HashDigest::new() - .chain(self.features.to_v1_bytes()) - .chain(self.commitment.as_bytes()) + .chain(self.features.to_consensus_bytes()) + .chain(self.commitment.to_consensus_bytes()) .chain(self.script.as_bytes()) - .chain(self.sender_offset_public_key.as_bytes()) - .chain(self.script_signature.u().as_bytes()) - .chain(self.script_signature.v().as_bytes()) - .chain(self.script_signature.public_nonce().as_bytes()) + .chain(self.sender_offset_public_key.to_consensus_bytes()) + .chain(self.script_signature.u().to_consensus_bytes()) + .chain(self.script_signature.v().to_consensus_bytes()) + .chain(self.script_signature.public_nonce().to_consensus_bytes()) .chain(self.input_data.as_bytes()) + .chain(self.covenant.to_consensus_bytes()) .finalize() .to_vec() } @@ -196,7 +205,7 @@ impl Display for TransactionInput { fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { write!( fmt, - "{} [{:?}], Script hash: ({}), Offset_Pubkey: ({})", + "{} [{:?}], Script: ({}), Offset_Pubkey: ({})", self.commitment.to_hex(), self.features, self.script, diff --git a/base_layer/core/src/transactions/transaction/transaction_output.rs b/base_layer/core/src/transactions/transaction/transaction_output.rs index 183f8f0345..9998ce4dbd 100644 --- a/base_layer/core/src/transactions/transaction/transaction_output.rs +++ b/base_layer/core/src/transactions/transaction/transaction_output.rs @@ -29,6 +29,7 @@ use std::{ }; use blake2::Digest; +use digest::FixedOutput; use rand::rngs::OsRng; use serde::{Deserialize, Serialize}; use tari_common_types::types::{ @@ -38,7 +39,6 @@ use tari_common_types::types::{ Commitment, CommitmentFactory, HashDigest, - MessageHash, PrivateKey, PublicKey, RangeProof, @@ -53,16 +53,20 @@ use tari_crypto::{ tari_utilities::{hex::Hex, ByteArray, Hashable}, }; -use crate::transactions::{ - tari_amount::MicroTari, - transaction, - transaction::{ - full_rewind_result::FullRewindResult, - rewind_result::RewindResult, - OutputFeatures, - OutputFlags, - TransactionError, - TransactionInput, +use crate::{ + consensus::ToConsensusBytes, + covenants::Covenant, + transactions::{ + tari_amount::MicroTari, + transaction, + transaction::{ + full_rewind_result::FullRewindResult, + rewind_result::RewindResult, + OutputFeatures, + OutputFlags, + TransactionError, + TransactionInput, + }, }, }; @@ -83,6 +87,8 @@ pub struct TransactionOutput { pub sender_offset_public_key: PublicKey, /// UTXO signature with the script offset private key, k_O pub metadata_signature: ComSignature, + /// The script that will be executed when spending this output + pub covenant: Covenant, } /// An output for a transaction, includes a range proof and Tari script metadata @@ -95,14 +101,16 @@ impl TransactionOutput { script: TariScript, sender_offset_public_key: PublicKey, metadata_signature: ComSignature, - ) -> TransactionOutput { - TransactionOutput { + covenant: Covenant, + ) -> Self { + Self { features, commitment, proof, script, sender_offset_public_key, metadata_signature, + covenant, } } @@ -135,10 +143,11 @@ impl TransactionOutput { &self.sender_offset_public_key, self.metadata_signature.public_nonce(), &self.commitment, + &self.covenant, ); if !self.metadata_signature.verify_challenge( &(&self.commitment + &self.sender_offset_public_key), - &challenge, + &challenge.finalize_fixed(), &PedersenCommitmentFactory::default(), ) { return Err(TransactionError::InvalidSignatureError( @@ -190,7 +199,7 @@ impl TransactionOutput { } /// Convenience function that returns the challenge for the metadata commitment signature - pub fn get_metadata_signature_challenge(&self, partial_commitment_nonce: Option<&PublicKey>) -> MessageHash { + pub fn get_metadata_signature_challenge(&self, partial_commitment_nonce: Option<&PublicKey>) -> Challenge { let nonce_commitment = match partial_commitment_nonce { None => self.metadata_signature.public_nonce().clone(), Some(partial_nonce) => self.metadata_signature.public_nonce() + partial_nonce, @@ -201,6 +210,7 @@ impl TransactionOutput { &self.sender_offset_public_key, &nonce_commitment, &self.commitment, + &self.covenant, ) } @@ -211,16 +221,15 @@ impl TransactionOutput { sender_offset_public_key: &PublicKey, public_commitment_nonce: &Commitment, commitment: &Commitment, - ) -> MessageHash { + covenant: &Covenant, + ) -> Challenge { Challenge::new() - .chain(public_commitment_nonce.as_bytes()) + .chain(public_commitment_nonce.to_consensus_bytes()) .chain(script.as_bytes()) - // TODO: Use consensus encoded bytes #testnet_reset - .chain(features.to_v1_bytes()) - .chain(sender_offset_public_key.as_bytes()) - .chain(commitment.as_bytes()) - .finalize() - .to_vec() + .chain(covenant.to_consensus_bytes()) + .chain(features.to_consensus_bytes()) + .chain(sender_offset_public_key.to_consensus_bytes()) + .chain(commitment.to_consensus_bytes()) } // Create commitment signature for the metadata @@ -232,6 +241,7 @@ impl TransactionOutput { sender_offset_public_key: &PublicKey, partial_commitment_nonce: Option<&PublicKey>, sender_offset_private_key: Option<&PrivateKey>, + covenant: &Covenant, ) -> Result { let nonce_a = PrivateKey::random(&mut OsRng); let nonce_b = PrivateKey::random(&mut OsRng); @@ -248,17 +258,18 @@ impl TransactionOutput { sender_offset_public_key, &nonce_commitment, &commitment, + covenant, ); let secret_x = match sender_offset_private_key { None => spending_key.clone(), - Some(key) => &spending_key.clone() + key, + Some(key) => spending_key + key, }; Ok(ComSignature::sign( value, secret_x, nonce_a, nonce_b, - &e, + &e.finalize_fixed(), &PedersenCommitmentFactory::default(), )?) } @@ -271,6 +282,7 @@ impl TransactionOutput { output_features: &OutputFeatures, sender_offset_public_key: &PublicKey, partial_commitment_nonce: &PublicKey, + covenant: &Covenant, ) -> Result { TransactionOutput::create_metadata_signature( value, @@ -280,6 +292,7 @@ impl TransactionOutput { sender_offset_public_key, Some(partial_commitment_nonce), None, + covenant, ) } @@ -290,6 +303,7 @@ impl TransactionOutput { script: &TariScript, output_features: &OutputFeatures, sender_offset_private_key: &PrivateKey, + covenant: &Covenant, ) -> Result { let sender_offset_public_key = PublicKey::from_secret_key(sender_offset_private_key); TransactionOutput::create_metadata_signature( @@ -300,6 +314,7 @@ impl TransactionOutput { &sender_offset_public_key, None, Some(sender_offset_private_key), + covenant, ) } @@ -317,7 +332,7 @@ impl TransactionOutput { /// Implement the canonical hashing function for TransactionOutput for use in ordering. impl Hashable for TransactionOutput { fn hash(&self) -> Vec { - transaction::hash_output(&self.features, &self.commitment, &self.script) + transaction::hash_output(&self.features, &self.commitment, &self.script, &self.covenant) } } @@ -330,6 +345,7 @@ impl Default for TransactionOutput { TariScript::default(), PublicKey::default(), ComSignature::default(), + Covenant::default(), ) } } diff --git a/base_layer/core/src/transactions/transaction/unblinded_output.rs b/base_layer/core/src/transactions/transaction/unblinded_output.rs index 33d921eea5..30344f8e0e 100644 --- a/base_layer/core/src/transactions/transaction/unblinded_output.rs +++ b/base_layer/core/src/transactions/transaction/unblinded_output.rs @@ -38,6 +38,7 @@ use tari_crypto::{ use crate::{ consensus::ConsensusEncodingSized, + covenants::Covenant, transactions::{ tari_amount::MicroTari, transaction, @@ -61,6 +62,7 @@ pub struct UnblindedOutput { pub spending_key: BlindingFactor, pub features: OutputFeatures, pub script: TariScript, + pub covenant: Covenant, pub input_data: ExecutionStack, pub script_private_key: PrivateKey, pub sender_offset_public_key: PublicKey, @@ -81,8 +83,9 @@ impl UnblindedOutput { sender_offset_public_key: PublicKey, metadata_signature: ComSignature, script_lock_height: u64, - ) -> UnblindedOutput { - UnblindedOutput { + covenant: Covenant, + ) -> Self { + Self { value, spending_key, features, @@ -92,6 +95,7 @@ impl UnblindedOutput { sender_offset_public_key, metadata_signature, script_lock_height, + covenant, } } @@ -126,6 +130,7 @@ impl UnblindedOutput { input_data: self.input_data.clone(), script_signature, sender_offset_public_key: self.sender_offset_public_key.clone(), + covenant: self.covenant.clone(), }) } @@ -149,6 +154,7 @@ impl UnblindedOutput { script: self.script.clone(), sender_offset_public_key: self.sender_offset_public_key.clone(), metadata_signature: self.metadata_signature.clone(), + covenant: self.covenant.clone(), }; Ok(output) @@ -185,6 +191,7 @@ impl UnblindedOutput { script: self.script.clone(), sender_offset_public_key: self.sender_offset_public_key.clone(), metadata_signature: self.metadata_signature.clone(), + covenant: self.covenant.clone(), }; Ok(output) @@ -198,7 +205,7 @@ impl UnblindedOutput { // Note: added to the struct to ensure the atomic nature between `commitment`, `spending_key` and `value`. pub fn hash(&self, factories: &CryptoFactories) -> Vec { let commitment = factories.commitment.commit_value(&self.spending_key, self.value.into()); - transaction::hash_output(&self.features, &commitment, &self.script) + transaction::hash_output(&self.features, &commitment, &self.script, &self.covenant) } } diff --git a/base_layer/core/src/transactions/transaction/unblinded_output_builder.rs b/base_layer/core/src/transactions/transaction/unblinded_output_builder.rs index 98796b968d..a531ea160c 100644 --- a/base_layer/core/src/transactions/transaction/unblinded_output_builder.rs +++ b/base_layer/core/src/transactions/transaction/unblinded_output_builder.rs @@ -23,9 +23,12 @@ use tari_common_types::types::{BlindingFactor, ComSignature, PrivateKey, PublicKey}; use tari_crypto::script::{ExecutionStack, TariScript}; -use crate::transactions::{ - tari_amount::MicroTari, - transaction::{OutputFeatures, TransactionError, TransactionOutput, UnblindedOutput}, +use crate::{ + covenants::Covenant, + transactions::{ + tari_amount::MicroTari, + transaction::{OutputFeatures, TransactionError, TransactionOutput, UnblindedOutput}, + }, }; #[derive(Debug, Clone)] @@ -34,6 +37,7 @@ pub struct UnblindedOutputBuilder { spending_key: BlindingFactor, pub features: OutputFeatures, pub script: Option, + covenant: Covenant, input_data: Option, script_private_key: Option, sender_offset_public_key: Option, @@ -49,6 +53,7 @@ impl UnblindedOutputBuilder { spending_key, features: OutputFeatures::default(), script: None, + covenant: Covenant::default(), input_data: None, script_private_key: None, sender_offset_public_key: None, @@ -74,6 +79,7 @@ impl UnblindedOutputBuilder { &self.features, &sender_offset_public_key, &public_nonce_commitment, + &self.covenant, )?; self.metadata_signature = Some(metadata_partial); self.metadata_signed_by_receiver = true; @@ -89,6 +95,7 @@ impl UnblindedOutputBuilder { .ok_or_else(|| TransactionError::ValidationError("script must be set".to_string()))?, &self.features, sender_offset_private_key, + &self.covenant, )?; self.metadata_signature = Some(metadata_sig); self.metadata_signed_by_sender = true; @@ -113,6 +120,7 @@ impl UnblindedOutputBuilder { script: self .script .ok_or_else(|| TransactionError::ValidationError("script must be set".to_string()))?, + covenant: self.covenant, input_data: self .input_data .ok_or_else(|| TransactionError::ValidationError("input_data must be set".to_string()))?, diff --git a/base_layer/core/src/transactions/transaction_protocol/proto/transaction_sender.proto b/base_layer/core/src/transactions/transaction_protocol/proto/transaction_sender.proto index 860317c3b5..dadeab0b33 100644 --- a/base_layer/core/src/transactions/transaction_protocol/proto/transaction_sender.proto +++ b/base_layer/core/src/transactions/transaction_protocol/proto/transaction_sender.proto @@ -27,8 +27,10 @@ message SingleRoundSenderData { bytes public_commitment_nonce = 9; // Output features tari.types.OutputFeatures features = 10; + // Covenant + bytes covenant = 11; // Unique id for NFTs - // bytes unique_id = 11; + // bytes unique_id = 12; } message TransactionSenderMessage { diff --git a/base_layer/core/src/transactions/transaction_protocol/proto/transaction_sender.rs b/base_layer/core/src/transactions/transaction_protocol/proto/transaction_sender.rs index 9f76cce023..d590c618f9 100644 --- a/base_layer/core/src/transactions/transaction_protocol/proto/transaction_sender.rs +++ b/base_layer/core/src/transactions/transaction_protocol/proto/transaction_sender.rs @@ -22,13 +22,16 @@ use std::convert::{TryFrom, TryInto}; -// The generated _oneof_ enum use proto::transaction_sender_message::Message as ProtoTxnSenderMessage; use tari_common_types::types::PublicKey; use tari_crypto::{script::TariScript, tari_utilities::ByteArray}; use super::{protocol as proto, protocol::transaction_sender_message::Message as ProtoTransactionSenderMessage}; -use crate::transactions::transaction_protocol::sender::{SingleRoundSenderData, TransactionSenderMessage}; +use crate::{ + consensus::{ConsensusDecoding, ToConsensusBytes}, + covenants::Covenant, + transactions::transaction_protocol::sender::{SingleRoundSenderData, TransactionSenderMessage}, +}; impl proto::TransactionSenderMessage { pub fn none() -> Self { @@ -103,6 +106,7 @@ impl TryFrom for SingleRoundSenderData { .features .map(TryInto::try_into) .ok_or_else(|| "Transaction output features not provided".to_string())??; + let covenant = Covenant::consensus_decode(&mut data.covenant.as_slice()).map_err(|err| err.to_string())?; Ok(Self { tx_id: data.tx_id.into(), @@ -115,6 +119,7 @@ impl TryFrom for SingleRoundSenderData { script: TariScript::from_bytes(&data.script).map_err(|err| err.to_string())?, sender_offset_public_key, public_commitment_nonce, + covenant, }) } } @@ -134,6 +139,7 @@ impl From for proto::SingleRoundSenderData { script: sender_data.script.as_bytes(), sender_offset_public_key: sender_data.sender_offset_public_key.to_vec(), public_commitment_nonce: sender_data.public_commitment_nonce.to_vec(), + covenant: sender_data.covenant.to_consensus_bytes(), } } } diff --git a/base_layer/core/src/transactions/transaction_protocol/recipient.rs b/base_layer/core/src/transactions/transaction_protocol/recipient.rs index 3b5a1fad4f..1aca21dc84 100644 --- a/base_layer/core/src/transactions/transaction_protocol/recipient.rs +++ b/base_layer/core/src/transactions/transaction_protocol/recipient.rs @@ -217,18 +217,21 @@ mod test { script::TariScript, }; - use crate::transactions::{ - crypto_factories::CryptoFactories, - tari_amount::*, - test_helpers::TestParams, - transaction::OutputFeatures, - transaction_protocol::{ - build_challenge, - sender::{SingleRoundSenderData, TransactionSenderMessage}, - RewindData, - TransactionMetadata, + use crate::{ + covenants::Covenant, + transactions::{ + crypto_factories::CryptoFactories, + tari_amount::*, + test_helpers::TestParams, + transaction::OutputFeatures, + transaction_protocol::{ + build_challenge, + sender::{SingleRoundSenderData, TransactionSenderMessage}, + RewindData, + TransactionMetadata, + }, + ReceiverTransactionProtocol, }, - ReceiverTransactionProtocol, }; #[test] @@ -253,6 +256,7 @@ mod test { script, sender_offset_public_key: p.sender_offset_public_key, public_commitment_nonce: p.sender_public_commitment_nonce, + covenant: Covenant::default(), }; let sender_info = TransactionSenderMessage::Single(Box::new(msg.clone())); let pubkey = PublicKey::from_secret_key(&p.spend_key); @@ -300,6 +304,7 @@ mod test { script, sender_offset_public_key: p.sender_offset_public_key, public_commitment_nonce: p.sender_public_commitment_nonce, + covenant: Covenant::default(), }; let sender_info = TransactionSenderMessage::Single(Box::new(msg)); let rewind_data = RewindData { diff --git a/base_layer/core/src/transactions/transaction_protocol/sender.rs b/base_layer/core/src/transactions/transaction_protocol/sender.rs index 9810550736..9fb8dbc1f6 100644 --- a/base_layer/core/src/transactions/transaction_protocol/sender.rs +++ b/base_layer/core/src/transactions/transaction_protocol/sender.rs @@ -22,7 +22,7 @@ use std::fmt; -use digest::Digest; +use digest::{Digest, FixedOutput}; use serde::{Deserialize, Serialize}; use tari_common_types::{ transaction::TxId, @@ -37,6 +37,7 @@ use tari_crypto::{ use crate::{ consensus::ConsensusConstants, + covenants::Covenant, transactions::{ crypto_factories::CryptoFactories, fee::Fee, @@ -79,6 +80,7 @@ pub(super) struct RawTransactionInfo { pub recipient_scripts: Vec, pub recipient_output_features: Vec, pub recipient_sender_offset_private_keys: Vec, + pub recipient_covenant: Vec, // The sender's portion of the public commitment nonce pub private_commitment_nonces: Vec, pub change: MicroTari, @@ -103,7 +105,7 @@ pub(super) struct RawTransactionInfo { pub recipient_info: RecipientInfo, pub signatures: Vec, pub message: String, - pub height: Option, + pub height: u64, pub prev_header: Option, } @@ -123,12 +125,14 @@ pub struct SingleRoundSenderData { pub message: String, /// The output's features pub features: OutputFeatures, - /// Script Hash + /// Script pub script: TariScript, /// Script offset public key pub sender_offset_public_key: PublicKey, /// The sender's portion of the public commitment nonce pub public_commitment_nonce: PublicKey, + /// Covenant + pub covenant: Covenant, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -366,6 +370,9 @@ impl SenderTransactionProtocol { let private_commitment_nonce = info.private_commitment_nonces.first().ok_or_else(|| { TPE::IncompleteStateError("The sender's private commitment nonce should be available".to_string()) })?; + let recipient_covenant = info.recipient_covenant.first().cloned().ok_or_else(|| { + TPE::IncompleteStateError("The recipient covenant should be available".to_string()) + })?; Ok(SingleRoundSenderData { tx_id: info.tx_id, @@ -378,6 +385,7 @@ impl SenderTransactionProtocol { script: recipient_script, sender_offset_public_key: PublicKey::from_secret_key(recipient_script_offset_secret_key), public_commitment_nonce: PublicKey::from_secret_key(private_commitment_nonce), + covenant: recipient_covenant, }) }, _ => Err(TPE::InvalidStateError), @@ -438,7 +446,9 @@ impl SenderTransactionProtocol { ) -> Result { // Create sender signature let public_commitment_nonce = PublicKey::from_secret_key(private_commitment_nonce); - let e = output.get_metadata_signature_challenge(Some(&public_commitment_nonce)); + let e = output + .get_metadata_signature_challenge(Some(&public_commitment_nonce)) + .finalize_fixed(); let sender_signature = Signature::sign(sender_offset_private_key.clone(), private_commitment_nonce.clone(), &e)?; let sender_signature = sender_signature.get_signature(); @@ -553,7 +563,7 @@ impl SenderTransactionProtocol { features: KernelFeatures, factories: &CryptoFactories, prev_header: Option, - height: Option, + height: u64, ) -> Result<(), TPE> { // Create the final aggregated signature, moving to the Failed state if anything goes wrong match &mut self.state { @@ -731,6 +741,7 @@ impl fmt::Display for SenderState { #[cfg(test)] mod test { + use digest::Digest; use rand::rngs::OsRng; use tari_common_types::types::{PrivateKey, PublicKey, RangeProof}; use tari_crypto::{ @@ -745,6 +756,7 @@ mod test { }; use crate::{ + covenants::Covenant, test_helpers::create_consensus_constants, transactions::{ crypto_factories::CryptoFactories, @@ -787,6 +799,7 @@ mod test { .unwrap(), ) .unwrap(); + let covenant = Covenant::default(); let partial_metadata_signature = TransactionOutput::create_partial_metadata_signature( &value.into(), @@ -795,6 +808,7 @@ mod test { &output_features, &sender_offset_public_key, &sender_public_commitment_nonce, + &covenant, ) .unwrap(); @@ -805,11 +819,14 @@ mod test { script, sender_offset_public_key, partial_metadata_signature.clone(), + covenant, ); assert!(!output.verify_metadata_signature().is_ok()); assert!(partial_metadata_signature.verify_challenge( &commitment, - &output.get_metadata_signature_challenge(Some(&sender_public_commitment_nonce)), + &output + .get_metadata_signature_challenge(Some(&sender_public_commitment_nonce)) + .finalize(), &commitment_factory )); @@ -851,10 +868,10 @@ mod test { p2.sender_offset_private_key.clone(), ) .unwrap(); - let mut sender = builder.build::(&factories, None, Some(u64::MAX)).unwrap(); + let mut sender = builder.build::(&factories, None, u64::MAX).unwrap(); assert!(!sender.is_failed()); assert!(sender.is_finalizing()); - match sender.finalize(KernelFeatures::empty(), &factories, None, Some(u64::MAX)) { + match sender.finalize(KernelFeatures::empty(), &factories, None, u64::MAX) { Ok(_) => (), Err(e) => panic!("{:?}", e), } @@ -881,11 +898,11 @@ mod test { .with_offset(a.offset.clone()) .with_private_nonce(a.nonce.clone()) .with_input(utxo.clone(), input) - .with_recipient_data(0, script.clone(), PrivateKey::random(&mut OsRng), features.clone(), PrivateKey::random(&mut OsRng)) + .with_recipient_data(0, script.clone(), PrivateKey::random(&mut OsRng), features.clone(), PrivateKey::random(&mut OsRng), Covenant::default()) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()) // A little twist: Check the case where the change is less than the cost of another output .with_amount(0, MicroTari(1200) - fee - MicroTari(10)); - let mut alice = builder.build::(&factories, None, Some(u64::MAX)).unwrap(); + let mut alice = builder.build::(&factories, None, u64::MAX).unwrap(); assert!(alice.is_single_round_message_ready()); let msg = alice.build_single_round_message().unwrap(); // Send message down the wire....and wait for response @@ -903,7 +920,7 @@ mod test { .unwrap(); // Transaction should be complete assert!(alice.is_finalizing()); - match alice.finalize(KernelFeatures::empty(), &factories, None, Some(u64::MAX)) { + match alice.finalize(KernelFeatures::empty(), &factories, None, u64::MAX) { Ok(_) => (), Err(e) => panic!("{:?}", e), }; @@ -947,10 +964,11 @@ mod test { PrivateKey::random(&mut OsRng), features.clone(), PrivateKey::random(&mut OsRng), + Covenant::default(), ) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()) .with_amount(0, MicroTari(5000)); - let mut alice = builder.build::(&factories, None, Some(u64::MAX)).unwrap(); + let mut alice = builder.build::(&factories, None, u64::MAX).unwrap(); assert!(alice.is_single_round_message_ready()); let msg = alice.build_single_round_message().unwrap(); println!( @@ -984,7 +1002,7 @@ mod test { .unwrap(); // Transaction should be complete assert!(alice.is_finalizing()); - match alice.finalize(KernelFeatures::empty(), &factories, None, Some(u64::MAX)) { + match alice.finalize(KernelFeatures::empty(), &factories, None, u64::MAX) { Ok(_) => (), Err(e) => panic!("{:?}", e), }; @@ -998,7 +1016,7 @@ mod test { assert_eq!(tx.body.outputs().len(), 2); assert!(tx .clone() - .validate_internal_consistency(false, &factories, None, None, Some(u64::MAX)) + .validate_internal_consistency(false, &factories, None, None, u64::MAX) .is_ok()); } @@ -1027,10 +1045,11 @@ mod test { PrivateKey::random(&mut OsRng), features.clone(), PrivateKey::random(&mut OsRng), + Covenant::default(), ) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()) .with_amount(0, (2u64.pow(32) + 1).into()); - let mut alice = builder.build::(&factories, None, Some(u64::MAX)).unwrap(); + let mut alice = builder.build::(&factories, None, u64::MAX).unwrap(); assert!(alice.is_single_round_message_ready()); let msg = alice.build_single_round_message().unwrap(); // Send message down the wire....and wait for response @@ -1072,10 +1091,11 @@ mod test { PrivateKey::random(&mut OsRng), Default::default(), PrivateKey::random(&mut OsRng), + Covenant::default(), ) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); // Verify that the initial 'fee greater than amount' check rejects the transaction when it is constructed - match builder.build::(&factories, None, Some(u64::MAX)) { + match builder.build::(&factories, None, u64::MAX) { Ok(_) => panic!("'BuildError(\"Fee is greater than amount\")' not caught"), Err(e) => assert_eq!(e.message, "Fee is greater than amount".to_string()), }; @@ -1105,10 +1125,11 @@ mod test { PrivateKey::random(&mut OsRng), Default::default(), PrivateKey::random(&mut OsRng), + Covenant::default(), ) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); // Test if the transaction passes the initial 'fee greater than amount' check when it is constructed - match builder.build::(&factories, None, Some(u64::MAX)) { + match builder.build::(&factories, None, u64::MAX) { Ok(_) => {}, Err(e) => panic!("Unexpected error: {:?}", e), }; @@ -1156,9 +1177,10 @@ mod test { PrivateKey::random(&mut OsRng), features.clone(), PrivateKey::random(&mut OsRng), + Covenant::default(), ) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); - let mut alice = builder.build::(&factories, None, Some(u64::MAX)).unwrap(); + let mut alice = builder.build::(&factories, None, u64::MAX).unwrap(); assert!(alice.is_single_round_message_ready()); let msg = alice.build_single_round_message().unwrap(); @@ -1186,7 +1208,7 @@ mod test { .unwrap(); // Transaction should be complete assert!(alice.is_finalizing()); - match alice.finalize(KernelFeatures::empty(), &factories, None, Some(u64::MAX)) { + match alice.finalize(KernelFeatures::empty(), &factories, None, u64::MAX) { Ok(_) => (), Err(e) => panic!("{:?}", e), }; diff --git a/base_layer/core/src/transactions/transaction_protocol/single_receiver.rs b/base_layer/core/src/transactions/transaction_protocol/single_receiver.rs index 6646a4cbc3..e87a4c9a4b 100644 --- a/base_layer/core/src/transactions/transaction_protocol/single_receiver.rs +++ b/base_layer/core/src/transactions/transaction_protocol/single_receiver.rs @@ -118,6 +118,7 @@ impl SingleReceiverTransactionProtocol { &sender_info.features, &sender_info.sender_offset_public_key, &sender_info.public_commitment_nonce, + &sender_info.covenant, )?; let output = TransactionOutput::new( @@ -128,6 +129,7 @@ impl SingleReceiverTransactionProtocol { sender_info.script.clone(), sender_info.sender_offset_public_key.clone(), partial_metadata_signature, + sender_info.covenant.clone(), ); Ok(output) } @@ -203,6 +205,7 @@ mod test { script, sender_offset_public_key, public_commitment_nonce, + covenant: Default::default(), }; let prot = SingleReceiverTransactionProtocol::create(&info, r, k.clone(), of, &factories, None).unwrap(); assert_eq!(prot.tx_id.as_u64(), 500, "tx_id is incorrect"); diff --git a/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs b/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs index d5d2ee36c6..60054fe896 100644 --- a/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs +++ b/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs @@ -25,7 +25,7 @@ use std::{ fmt::{Debug, Error, Formatter}, }; -use digest::Digest; +use digest::{Digest, FixedOutput}; use log::*; use rand::rngs::OsRng; use tari_common_types::{ @@ -42,6 +42,7 @@ use tari_crypto::{ use crate::{ consensus::{ConsensusConstants, ConsensusEncodingSized}, + covenants::Covenant, transactions::{ crypto_factories::CryptoFactories, fee::Fee, @@ -88,6 +89,7 @@ pub struct SenderTransactionInitializer { change_input_data: Option, change_script_private_key: Option, change_sender_offset_private_key: Option, + change_covenant: Covenant, rewind_data: Option, offset: Option, excess_blinding_factor: BlindingFactor, @@ -97,6 +99,7 @@ pub struct SenderTransactionInitializer { recipient_output_features: FixedSet, recipient_scripts: FixedSet, recipient_sender_offset_private_keys: FixedSet, + recipient_covenants: FixedSet, private_commitment_nonces: FixedSet, tx_id: Option, fee: Fee, @@ -130,6 +133,7 @@ impl SenderTransactionInitializer { change_input_data: None, change_script_private_key: None, change_sender_offset_private_key: None, + change_covenant: Covenant::default(), rewind_data: None, offset: None, private_nonce: None, @@ -139,6 +143,7 @@ impl SenderTransactionInitializer { recipient_output_features: FixedSet::new(num_recipients), recipient_scripts: FixedSet::new(num_recipients), recipient_sender_offset_private_keys: FixedSet::new(num_recipients), + recipient_covenants: FixedSet::new(num_recipients), private_commitment_nonces: FixedSet::new(num_recipients), tx_id: None, } @@ -166,6 +171,7 @@ impl SenderTransactionInitializer { recipient_sender_offset_private_key: PrivateKey, recipient_output_features: OutputFeatures, private_commitment_nonce: PrivateKey, + covenant: Covenant, ) -> &mut Self { self.recipient_output_features .set_item(receiver_index, recipient_output_features); @@ -174,6 +180,7 @@ impl SenderTransactionInitializer { .set_item(receiver_index, recipient_sender_offset_private_key); self.private_commitment_nonces .set_item(receiver_index, private_commitment_nonce); + self.recipient_covenants.set_item(receiver_index, covenant); self } @@ -213,10 +220,11 @@ impl SenderTransactionInitializer { &output.sender_offset_public_key, output.metadata_signature.public_nonce(), &commitment, + &output.covenant, ); if !output.metadata_signature.verify_challenge( &(&commitment + &output.sender_offset_public_key), - &e, + &e.finalize_fixed(), &commitment_factory, ) { self.clone().build_err(&*format!( @@ -351,7 +359,7 @@ impl SenderTransactionInitializer { None => Ok((fee_without_change + v, MicroTari(0), None)), Some(MicroTari(0)) => Ok((fee_without_change + v, MicroTari(0), None)), Some(v) => { - let script = self + let change_script = self .change_script .as_ref() .ok_or("Change script was not provided")? @@ -363,16 +371,17 @@ impl SenderTransactionInitializer { let metadata_signature = TransactionOutput::create_final_metadata_signature( &v, &change_key.clone(), - &script, + &change_script, &output_features, &change_sender_offset_private_key, + &self.change_covenant, ) .map_err(|e| e.to_string())?; let change_unblinded_output = UnblindedOutput::new( v, change_key.clone(), output_features, - script, + change_script, self.change_input_data .as_ref() .ok_or("Change script was not provided")? @@ -384,6 +393,7 @@ impl SenderTransactionInitializer { PublicKey::from_secret_key(&change_sender_offset_private_key), metadata_signature, 0, + self.change_covenant.clone(), ); Ok((fee_with_change, v, Some(change_unblinded_output))) }, @@ -428,7 +438,7 @@ impl SenderTransactionInitializer { mut self, factories: &CryptoFactories, prev_header: Option, - height: Option, + height: u64, ) -> Result { // Compile a list of all data that is missing let mut message = Vec::new(); @@ -609,6 +619,7 @@ impl SenderTransactionInitializer { recipient_output_features, recipient_scripts: self.recipient_scripts.into_vec(), recipient_sender_offset_private_keys: self.recipient_sender_offset_private_keys.into_vec(), + recipient_covenant: self.recipient_covenants.into_vec(), private_commitment_nonces: self.private_commitment_nonces.into_vec(), change, unblinded_change_output: change_output, @@ -658,6 +669,7 @@ mod test { }; use crate::{ + covenants::Covenant, test_helpers::create_consensus_constants, transactions::{ crypto_factories::CryptoFactories, @@ -681,7 +693,7 @@ mod test { let p = TestParams::new(); // Start the builder let builder = SenderTransactionInitializer::new(0, create_consensus_constants(0)); - let err = builder.build::(&factories, None, Some(u64::MAX)).unwrap_err(); + let err = builder.build::(&factories, None, u64::MAX).unwrap_err(); let script = script!(Nop); // We should have a bunch of fields missing still, but we can recover and continue assert_eq!( @@ -714,16 +726,17 @@ mod test { PrivateKey::random(&mut OsRng), Default::default(), PrivateKey::random(&mut OsRng), + Covenant::default(), ) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); let expected_fee = builder.fee().calculate(MicroTari(20), 1, 1, 2, 0); // We needed a change input, so this should fail - let err = builder.build::(&factories, None, Some(u64::MAX)).unwrap_err(); + let err = builder.build::(&factories, None, u64::MAX).unwrap_err(); assert_eq!(err.message, "Change spending key was not provided"); // Ok, give them a change output let mut builder = err.builder; builder.with_change_secret(p.change_spend_key); - let result = builder.build::(&factories, None, Some(u64::MAX)).unwrap(); + let result = builder.build::(&factories, None, u64::MAX).unwrap(); // Peek inside and check the results if let SenderState::Finalizing(info) = result.into_state() { assert_eq!(info.num_recipients, 0, "Number of receivers"); @@ -765,7 +778,7 @@ mod test { .with_input(utxo, input) .with_fee_per_gram(MicroTari(4)) .with_prevent_fee_gt_amount(false); - let result = builder.build::(&factories, None, Some(u64::MAX)).unwrap(); + let result = builder.build::(&factories, None, u64::MAX).unwrap(); // Peek inside and check the results if let SenderState::Finalizing(info) = result.into_state() { assert_eq!(info.num_recipients, 0, "Number of receivers"); @@ -816,7 +829,7 @@ mod test { .with_input(utxo, input) .with_fee_per_gram(MicroTari(1)) .with_prevent_fee_gt_amount(false); - let result = builder.build::(&factories, None, Some(u64::MAX)).unwrap(); + let result = builder.build::(&factories, None, u64::MAX).unwrap(); // Peek inside and check the results if let SenderState::Finalizing(info) = result.into_state() { assert_eq!(info.num_recipients, 0, "Number of receivers"); @@ -858,7 +871,7 @@ mod test { let (utxo, input) = create_test_input(MicroTari(50), 0, &factories.commitment); builder.with_input(utxo, input); } - let err = builder.build::(&factories, None, Some(u64::MAX)).unwrap_err(); + let err = builder.build::(&factories, None, u64::MAX).unwrap_err(); assert_eq!(err.message, "Too many inputs in transaction"); } @@ -889,9 +902,10 @@ mod test { PrivateKey::random(&mut OsRng), Default::default(), PrivateKey::random(&mut OsRng), + Covenant::default(), ); // .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); - let err = builder.build::(&factories, None, Some(u64::MAX)).unwrap_err(); + let err = builder.build::(&factories, None, u64::MAX).unwrap_err(); assert_eq!(err.message, "Fee is less than the minimum"); } @@ -921,9 +935,10 @@ mod test { PrivateKey::random(&mut OsRng), Default::default(), PrivateKey::random(&mut OsRng), + Covenant::default(), ) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); - let err = builder.build::(&factories, None, Some(u64::MAX)).unwrap_err(); + let err = builder.build::(&factories, None, u64::MAX).unwrap_err(); assert_eq!( err.message, "You are spending (471 µT) more than you're providing (400 µT)." @@ -958,6 +973,7 @@ mod test { PrivateKey::random(&mut OsRng), Default::default(), PrivateKey::random(&mut OsRng), + Covenant::default(), ) .with_recipient_data( 1, @@ -965,9 +981,10 @@ mod test { PrivateKey::random(&mut OsRng), Default::default(), PrivateKey::random(&mut OsRng), + Covenant::default(), ) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); - let result = builder.build::(&factories, None, Some(u64::MAX)).unwrap(); + let result = builder.build::(&factories, None, u64::MAX).unwrap(); // Peek inside and check the results if let SenderState::Failed(TransactionProtocolError::UnsupportedError(s)) = result.into_state() { assert_eq!(s, "Multiple recipients are not supported yet") @@ -1013,9 +1030,10 @@ mod test { PrivateKey::random(&mut OsRng), Default::default(), PrivateKey::random(&mut OsRng), + Covenant::default(), ) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); - let result = builder.build::(&factories, None, Some(u64::MAX)).unwrap(); + let result = builder.build::(&factories, None, u64::MAX).unwrap(); // Peek inside and check the results if let SenderState::SingleRoundMessageReady(info) = result.into_state() { assert_eq!(info.num_recipients, 1, "Number of receivers"); @@ -1064,9 +1082,10 @@ mod test { PrivateKey::random(&mut OsRng), Default::default(), PrivateKey::random(&mut OsRng), + Covenant::default(), ) .with_change_script(script, ExecutionStack::default(), PrivateKey::default()); - let result = builder.build::(&factories, None, Some(u64::MAX)); + let result = builder.build::(&factories, None, u64::MAX); match result { Ok(_) => panic!("Range proof should have failed to verify"), diff --git a/base_layer/core/src/validation/block_validators/async_validator.rs b/base_layer/core/src/validation/block_validators/async_validator.rs index aa27ff65d9..27534a8b71 100644 --- a/base_layer/core/src/validation/block_validators/async_validator.rs +++ b/base_layer/core/src/validation/block_validators/async_validator.rs @@ -154,6 +154,8 @@ impl BlockValidator { AggregateBody::new_sorted_unchecked(inputs_result.inputs, outputs_result.outputs, kernels_result.kernels), ); + helpers::validate_covenants(&block)?; + Ok(block) } diff --git a/base_layer/core/src/validation/block_validators/body_only.rs b/base_layer/core/src/validation/block_validators/body_only.rs index 0955f01402..6047e1401c 100644 --- a/base_layer/core/src/validation/block_validators/body_only.rs +++ b/base_layer/core/src/validation/block_validators/body_only.rs @@ -88,6 +88,7 @@ impl PostOrphanBodyValidation for BodyOnlyValidator { let mmr_roots = chain_storage::calculate_mmr_roots(backend, block.block())?; helpers::check_mmr_roots(block.header(), &mmr_roots)?; helpers::check_not_bad_block(backend, block.hash())?; + helpers::validate_covenants(block.block())?; trace!( target: LOG_TARGET, "Block validation: MMR roots are valid for {}", diff --git a/base_layer/core/src/validation/block_validators/test.rs b/base_layer/core/src/validation/block_validators/test.rs index ddb4162e6a..814a9e55e0 100644 --- a/base_layer/core/src/validation/block_validators/test.rs +++ b/base_layer/core/src/validation/block_validators/test.rs @@ -29,6 +29,7 @@ use tari_test_utils::unpack_enum; use crate::{ blocks::ChainBlock, consensus::{ConsensusConstantsBuilder, ConsensusManager}, + covenants::CovenantError, test_helpers::{ blockchain::{TempDatabase, TestBlockchain}, BlockSpec, diff --git a/base_layer/core/src/validation/error.rs b/base_layer/core/src/validation/error.rs index 1c934630ad..8c73ace83a 100644 --- a/base_layer/core/src/validation/error.rs +++ b/base_layer/core/src/validation/error.rs @@ -27,6 +27,7 @@ use tokio::task; use crate::{ blocks::{BlockHeaderValidationError, BlockValidationError}, chain_storage::ChainStorageError, + covenants::CovenantError, proof_of_work::{monero_rx::MergeMineError, PowError}, transactions::transaction::TransactionError, }; @@ -105,6 +106,8 @@ pub enum ValidationError { }, #[error("Consensus Error: {0}")] ConsensusError(String), + #[error("Covenant failed to validate: {0}")] + CovenantError(#[from] CovenantError), } // ChainStorageError has a ValidationError variant, so to prevent a cyclic dependency we use a string representation in diff --git a/base_layer/core/src/validation/helpers.rs b/base_layer/core/src/validation/helpers.rs index 2744455d8c..26848d5da4 100644 --- a/base_layer/core/src/validation/helpers.rs +++ b/base_layer/core/src/validation/helpers.rs @@ -248,7 +248,7 @@ pub fn check_accounting_balance( total_coinbase, factories, Some(block.header.prev_hash.clone()), - Some(block.header.height), + block.header.height, ) .map_err(|err| { warn!( @@ -639,6 +639,16 @@ pub fn check_not_bad_block(db: &B, hash: &[u8]) -> Result< Ok(()) } +pub fn validate_covenants(block: &Block) -> Result<(), ValidationError> { + for input in block.body.inputs() { + let output_set_size = input + .covenant + .execute(block.header.height, input, block.body.outputs())?; + trace!(target: LOG_TARGET, "{} output(s) passed covenant", output_set_size); + } + Ok(()) +} + pub fn check_coinbase_reward( factory: &CommitmentFactory, rules: &ConsensusManager, @@ -787,13 +797,13 @@ mod test { fn it_checks_the_kernel_timelock() { let mut kernel = test_helpers::create_test_kernel(0.into(), 0); kernel.lock_height = 2; - assert_eq!( + assert!(matches!( check_kernel_lock_height(1, &[kernel.clone()]), Err(BlockValidationError::MaturityError) - ); + )); - assert_eq!(check_kernel_lock_height(2, &[kernel.clone()]), Ok(())); - assert_eq!(check_kernel_lock_height(3, &[kernel]), Ok(())); + check_kernel_lock_height(2, &[kernel.clone()]).unwrap(); + check_kernel_lock_height(3, &[kernel]).unwrap(); } } @@ -809,22 +819,23 @@ mod test { Default::default(), Default::default(), Default::default(), + Default::default(), ); input.features.maturity = 5; - assert_eq!( + assert!(matches!( check_maturity(1, &[input.clone()]), Err(TransactionError::InputMaturity) - ); + )); - assert_eq!( + assert!(matches!( check_maturity(4, &[input.clone()]), Err(TransactionError::InputMaturity) - ); + )); - assert_eq!(check_maturity(5, &[input.clone()]), Ok(())); - assert_eq!(check_maturity(6, &[input]), Ok(())); + check_maturity(5, &[input.clone()]).unwrap(); + check_maturity(6, &[input]).unwrap(); } } } diff --git a/base_layer/core/src/validation/test.rs b/base_layer/core/src/validation/test.rs index 9354504fd2..b597bedc5b 100644 --- a/base_layer/core/src/validation/test.rs +++ b/base_layer/core/src/validation/test.rs @@ -20,6 +20,8 @@ // 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 crate::validation::MempoolTransactionValidation; +use crate::validation::transaction_validators::TxInternalConsistencyValidator; use std::sync::Arc; use tari_common::configuration::Network; @@ -31,6 +33,7 @@ use crate::{ blocks::{BlockHeader, BlockHeaderAccumulatedData, ChainBlock, ChainHeader}, chain_storage::DbTransaction, consensus::{ConsensusConstantsBuilder, ConsensusManager, ConsensusManagerBuilder}, + covenants::Covenant, proof_of_work::AchievedTargetDifficulty, test_helpers::{blockchain::create_store_with_consensus, create_chain_header}, transactions::{ @@ -42,52 +45,56 @@ use crate::{ validation::{header_iter::HeaderIter, ChainBalanceValidator, FinalHorizonStateValidation}, }; -#[test] -fn header_iter_empty_and_invalid_height() { - let consensus_manager = ConsensusManager::builder(Network::LocalNet).build(); - let genesis = consensus_manager.get_genesis_block(); - let db = create_store_with_consensus(consensus_manager); +mod header_validators { + use super::*; - let iter = HeaderIter::new(&db, 0, 10); - let headers = iter.map(Result::unwrap).collect::>(); - assert_eq!(headers.len(), 1); + #[test] + fn header_iter_empty_and_invalid_height() { + let consensus_manager = ConsensusManager::builder(Network::LocalNet).build(); + let genesis = consensus_manager.get_genesis_block(); + let db = create_store_with_consensus(consensus_manager); - assert_eq!(genesis.header(), &headers[0]); + let iter = HeaderIter::new(&db, 0, 10); + let headers = iter.map(Result::unwrap).collect::>(); + assert_eq!(headers.len(), 1); - // Invalid header height - let iter = HeaderIter::new(&db, 1, 10); - let headers = iter.collect::, _>>().unwrap(); - assert_eq!(headers.len(), 1); -} + assert_eq!(genesis.header(), &headers[0]); -#[test] -fn header_iter_fetch_in_chunks() { - let consensus_manager = ConsensusManagerBuilder::new(Network::LocalNet).build(); - let db = create_store_with_consensus(consensus_manager.clone()); - let headers = (1..=15).fold(vec![db.fetch_chain_header(0).unwrap()], |mut acc, i| { - let prev = acc.last().unwrap(); - let mut header = BlockHeader::new(0); - header.height = i; - header.prev_hash = prev.hash().clone(); - // These have to be unique - header.kernel_mmr_size = 2 + i; - header.output_mmr_size = 4001 + i; - - let chain_header = create_chain_header(header, prev.accumulated_data()); - acc.push(chain_header); - acc - }); - db.insert_valid_headers(headers.into_iter().skip(1).collect()).unwrap(); - - let iter = HeaderIter::new(&db, 11, 3); - let headers = iter.map(Result::unwrap).collect::>(); - assert_eq!(headers.len(), 12); - let genesis = consensus_manager.get_genesis_block(); - assert_eq!(genesis.header(), &headers[0]); + // Invalid header height + let iter = HeaderIter::new(&db, 1, 10); + let headers = iter.collect::, _>>().unwrap(); + assert_eq!(headers.len(), 1); + } - (1..=11).for_each(|i| { - assert_eq!(headers[i].height, i as u64); - }) + #[test] + fn header_iter_fetch_in_chunks() { + let consensus_manager = ConsensusManagerBuilder::new(Network::LocalNet).build(); + let db = create_store_with_consensus(consensus_manager.clone()); + let headers = (1..=15).fold(vec![db.fetch_chain_header(0).unwrap()], |mut acc, i| { + let prev = acc.last().unwrap(); + let mut header = BlockHeader::new(0); + header.height = i; + header.prev_hash = prev.hash().clone(); + // These have to be unique + header.kernel_mmr_size = 2 + i; + header.output_mmr_size = 4001 + i; + + let chain_header = create_chain_header(header, prev.accumulated_data()); + acc.push(chain_header); + acc + }); + db.insert_valid_headers(headers.into_iter().skip(1).collect()).unwrap(); + + let iter = HeaderIter::new(&db, 11, 3); + let headers = iter.map(Result::unwrap).collect::>(); + assert_eq!(headers.len(), 12); + let genesis = consensus_manager.get_genesis_block(); + assert_eq!(genesis.header(), &headers[0]); + + (1..=11).for_each(|i| { + assert_eq!(headers[i].height, i as u64); + }) + } } #[test] @@ -98,7 +105,13 @@ fn chain_balance_validation() { let consensus_manager = ConsensusManagerBuilder::new(Network::Weatherwax).build(); let genesis = consensus_manager.get_genesis_block(); let faucet_value = 5000 * uT; - let (faucet_utxo, faucet_key, _) = create_utxo(faucet_value, &factories, OutputFeatures::default(), &script!(Nop)); + let (faucet_utxo, faucet_key, _) = create_utxo( + faucet_value, + &factories, + OutputFeatures::default(), + &script!(Nop), + &Covenant::default(), + ); let (pk, sig) = create_random_signature_from_s_key(faucet_key, 0.into(), 0); let excess = Commitment::from_public_key(&pk); let kernel = TransactionKernel { @@ -149,6 +162,7 @@ fn chain_balance_validation() { &factories, OutputFeatures::create_coinbase(1), &script!(Nop), + &Covenant::default(), ); // let _coinbase_hash = coinbase.hash(); let (pk, sig) = create_random_signature_from_s_key(coinbase_key, 0.into(), 0); @@ -195,7 +209,13 @@ fn chain_balance_validation() { let mut txn = DbTransaction::new(); let v = consensus_manager.get_block_reward_at(2) + uT; - let (coinbase, key, _) = create_utxo(v, &factories, OutputFeatures::create_coinbase(1), &script!(Nop)); + let (coinbase, key, _) = create_utxo( + v, + &factories, + OutputFeatures::create_coinbase(1), + &script!(Nop), + &Covenant::default(), + ); let (pk, sig) = create_random_signature_from_s_key(key, 0.into(), 0); let excess = Commitment::from_public_key(&pk); let kernel = KernelBuilder::new() @@ -235,3 +255,49 @@ fn chain_balance_validation() { .validate(&*db.db_read_access().unwrap(), 2, &utxo_sum, &kernel_sum) .unwrap_err(); } + +mod transaction_validators { + use super::*; + + mod tx_internal_consistency_validator { + use crate::transactions::test_helpers::schema_to_transaction; + use super::*; + + #[test] + fn it_validates_covenants() { + let consensus_manager = ConsensusManager::builder(Network::LocalNet).build(); + let genesis = consensus_manager.get_genesis_block(); + let db = create_store_with_consensus(consensus_manager); + let validator = TxInternalConsistencyValidator::new( + CryptoFactories::default(), + true, + db + ); + + let unique_id = b"dank-meme-nft".to_vec(); + let covenant = covenant!(fields_preserved(@fields(@field::features_unique_id, @field::covenant))); + let mut schema = txn_schema!(from: vec![coinbase_a], to: vec![100 * T]); + // schema.script = script!(CheckHeight(1) IfThen Nop Else b); + schema.features.unique_id = Some(unique_id.clone()); + schema.covenant = covenant.clone(); + let (txs, outputs) = schema_to_transaction(&[schema]); + + validator.validate( + &tx, + ) + + et txs = txs.into_iter().map(|t| Arc::try_unwrap(t).unwrap()).collect::>(); + let (block, _) = blockchain.add_block("B", "A", BlockSpec::new().with_transactions(txs).finish()); + + let mut schema = txn_schema!(from: vec![outputs[0].clone()], to: vec![50 * T]); + schema.features.unique_id = Some(unique_id); + schema.covenant = covenant; + + let (txs, _) = schema_to_transaction(&[schema]); + let txs = txs.into_iter().map(|t| Arc::try_unwrap(t).unwrap()).collect::>(); + let (block, _) = blockchain.create_next_tip(BlockSpec::new().with_transactions(txs).finish()); + let err = validator.validate_block_body(block.block().clone()).await.unwrap_err(); + unpack_enum!(ValidationError::CovenantError(CovenantError::NoMatchingOutputs) = err); + } + } +} diff --git a/base_layer/core/src/validation/transaction_validators.rs b/base_layer/core/src/validation/transaction_validators.rs index 631018f1ba..dfe3979048 100644 --- a/base_layer/core/src/validation/transaction_validators.rs +++ b/base_layer/core/src/validation/transaction_validators.rs @@ -67,7 +67,7 @@ impl MempoolTransactionValidation for TxInternalConsistenc &self.factories, None, Some(tip.best_block().clone()), - Some(tip.height_of_longest_chain()), + tip.height_of_longest_chain(), ) .map_err(ValidationError::TransactionError)?; Ok(()) diff --git a/base_layer/core/tests/block_validation.rs b/base_layer/core/tests/block_validation.rs index c045e345af..57cfc49a2c 100644 --- a/base_layer/core/tests/block_validation.rs +++ b/base_layer/core/tests/block_validation.rs @@ -227,6 +227,7 @@ async fn inputs_are_not_malleable() { script: spent_output.script.clone(), input_data: Some(inputs![malicious_script_public_key]), output_features: spent_output.features, + ..Default::default() }); let input_mut = block.body.inputs_mut().get_mut(0).unwrap(); diff --git a/base_layer/core/tests/helpers/block_builders.rs b/base_layer/core/tests/helpers/block_builders.rs index b145b4a175..8728f24732 100644 --- a/base_layer/core/tests/helpers/block_builders.rs +++ b/base_layer/core/tests/helpers/block_builders.rs @@ -30,6 +30,7 @@ use tari_core::{ blocks::{Block, BlockHeader, BlockHeaderAccumulatedData, ChainBlock, ChainHeader, NewBlockTemplate}, chain_storage::{BlockAddResult, BlockchainBackend, BlockchainDatabase, ChainStorageError}, consensus::{emission::Emission, ConsensusConstants, ConsensusManager, ConsensusManagerBuilder}, + covenants::Covenant, proof_of_work::{sha3_difficulty, AchievedTargetDifficulty, Difficulty}, transactions::{ tari_amount::MicroTari, @@ -114,7 +115,13 @@ fn print_new_genesis_block() { let factories = CryptoFactories::default(); let mut header = BlockHeader::new(consensus_manager.consensus_constants(0).blockchain_version()); let value = consensus_manager.emission_schedule().block_reward(0); - let (utxo, key, _) = create_utxo(value, &factories, OutputFeatures::create_coinbase(1), &script![Nop]); + let (utxo, key, _) = create_utxo( + value, + &factories, + OutputFeatures::create_coinbase(1), + &script![Nop], + &Covenant::default(), + ); let (pk, sig) = create_random_signature_from_s_key(key.clone(), 0.into(), 0); let excess = Commitment::from_public_key(&pk); let kernel = KernelBuilder::new() diff --git a/base_layer/core/tests/mempool.rs b/base_layer/core/tests/mempool.rs index daced7e38f..632f18a7f9 100644 --- a/base_layer/core/tests/mempool.rs +++ b/base_layer/core/tests/mempool.rs @@ -1067,7 +1067,7 @@ async fn consensus_validation_large_tx() { // make sure the tx was correctly made and is valid let factories = CryptoFactories::default(); assert!(tx - .validate_internal_consistency(true, &factories, None, None, Some(u64::MAX)) + .validate_internal_consistency(true, &factories, None, None, u64::MAX) .is_ok()); let weighting = constants.transaction_weight(); let weight = tx.calculate_weight(weighting); diff --git a/base_layer/core/tests/node_comms_interface.rs b/base_layer/core/tests/node_comms_interface.rs index 923dea8f4b..22bae72f45 100644 --- a/base_layer/core/tests/node_comms_interface.rs +++ b/base_layer/core/tests/node_comms_interface.rs @@ -34,6 +34,7 @@ use tari_core::{ }, chain_storage::{BlockchainDatabaseConfig, DbTransaction, Validators}, consensus::ConsensusManager, + covenants::Covenant, mempool::{Mempool, MempoolConfig}, test_helpers::{ blockchain::{create_store_with_consensus_and_validators_and_config, create_test_blockchain_db}, @@ -187,6 +188,7 @@ async fn inbound_fetch_utxos() { &factories, Default::default(), &TariScript::default(), + &Covenant::default(), ); let hash_2 = utxo_2.hash(); @@ -226,18 +228,21 @@ async fn inbound_fetch_txos() { &factories, Default::default(), &TariScript::default(), + &Covenant::default(), ); let (pruned_utxo, _, _) = create_utxo( MicroTari(10_000), &factories, Default::default(), &TariScript::default(), + &Covenant::default(), ); let (stxo, _, _) = create_utxo( MicroTari(10_000), &factories, Default::default(), &TariScript::default(), + &Covenant::default(), ); let utxo_hash = utxo.hash(); let stxo_hash = stxo.hash(); @@ -338,13 +343,20 @@ async fn inbound_fetch_blocks_before_horizon_height() { outbound_nci, ); let script = script!(Nop); - let (utxo, key, offset) = create_utxo(MicroTari(10_000), &factories, Default::default(), &script); + let (utxo, key, offset) = create_utxo( + MicroTari(10_000), + &factories, + Default::default(), + &script, + &Covenant::default(), + ); let metadata_signature = TransactionOutput::create_final_metadata_signature( &MicroTari(10_000), &key, &script, &OutputFeatures::default(), &offset, + &Covenant::default(), ) .unwrap(); let unblinded_output = UnblindedOutput::new( @@ -357,6 +369,7 @@ async fn inbound_fetch_blocks_before_horizon_height() { PublicKey::from_secret_key(&offset), metadata_signature, 0, + Covenant::default(), ); let mut txn = DbTransaction::new(); txn.insert_utxo(utxo.clone(), block0.hash().clone(), 0, 4002);