diff --git a/console/program/src/state_path/configuration/mod.rs b/console/program/src/state_path/configuration/mod.rs index bc6939fa63..cb3d3556cc 100644 --- a/console/program/src/state_path/configuration/mod.rs +++ b/console/program/src/state_path/configuration/mod.rs @@ -57,6 +57,10 @@ pub type TransactionsTree = BHPMerkleTree; /// The Merkle path for a transaction in a block. pub type TransactionsPath = MerklePath; +/// The Merkle tree for the execution. +pub type ExecutionTree = BHPMerkleTree; +/// The Merkle tree for the deployment. +pub type DeploymentTree = BHPMerkleTree; /// The Merkle tree for the transaction. pub type TransactionTree = BHPMerkleTree; /// The Merkle path for a function or transition in the transaction. diff --git a/ledger/block/src/transaction/bytes.rs b/ledger/block/src/transaction/bytes.rs index 18cdbad312..be8aca9d84 100644 --- a/ledger/block/src/transaction/bytes.rs +++ b/ledger/block/src/transaction/bytes.rs @@ -96,8 +96,9 @@ impl ToBytes for Transaction { 1u8.write_le(&mut writer)?; // Write the transaction. + // We don't write the deployment or execution id, which are recomputed when creating the transaction. match self { - Self::Deploy(id, owner, deployment, fee) => { + Self::Deploy(id, _, owner, deployment, fee) => { // Write the variant. 0u8.write_le(&mut writer)?; // Write the ID. @@ -109,7 +110,7 @@ impl ToBytes for Transaction { // Write the fee. fee.write_le(&mut writer) } - Self::Execute(id, execution, fee) => { + Self::Execute(id, _, execution, fee) => { // Write the variant. 1u8.write_le(&mut writer)?; // Write the ID. diff --git a/ledger/block/src/transaction/deployment/mod.rs b/ledger/block/src/transaction/deployment/mod.rs index 42c6182f6f..92972ba956 100644 --- a/ledger/block/src/transaction/deployment/mod.rs +++ b/ledger/block/src/transaction/deployment/mod.rs @@ -105,6 +105,16 @@ impl Deployment { Ok(u64::try_from(self.to_bytes_le()?.len())?) } + /// Returns the number of program functions in the deployment. + pub fn len(&self) -> usize { + self.program.functions().len() + } + + /// Returns `true` if the deployment is empty. + pub fn is_empty(&self) -> bool { + self.program.functions().is_empty() + } + /// Returns the edition. pub const fn edition(&self) -> u16 { self.edition @@ -161,7 +171,7 @@ impl Deployment { /// Returns the deployment ID. pub fn to_deployment_id(&self) -> Result> { - Ok(*Transaction::deployment_tree(self, None)?.root()) + Ok(*Transaction::deployment_tree(self)?.root()) } } diff --git a/ledger/block/src/transaction/execution/mod.rs b/ledger/block/src/transaction/execution/mod.rs index 36139105c5..c2050693b3 100644 --- a/ledger/block/src/transaction/execution/mod.rs +++ b/ledger/block/src/transaction/execution/mod.rs @@ -70,7 +70,7 @@ impl Execution { /// Returns the execution ID. pub fn to_execution_id(&self) -> Result> { - Ok(*Transaction::execution_tree(self, &None)?.root()) + Ok(*Transaction::execution_tree(self)?.root()) } } @@ -157,6 +157,6 @@ pub mod test_helpers { // Retrieve a transaction. let transaction = block.transactions().iter().next().unwrap().deref().clone(); // Retrieve the execution. - if let Transaction::Execute(_, execution, _) = transaction { execution } else { unreachable!() } + if let Transaction::Execute(_, _, execution, _) = transaction { execution } else { unreachable!() } } } diff --git a/ledger/block/src/transaction/merkle.rs b/ledger/block/src/transaction/merkle.rs index d6e5d4fa56..09d7e53409 100644 --- a/ledger/block/src/transaction/merkle.rs +++ b/ledger/block/src/transaction/merkle.rs @@ -27,7 +27,7 @@ impl Transaction { /// Returns the Merkle leaf for the given ID of a function or transition in the transaction. pub fn to_leaf(&self, id: &Field) -> Result> { match self { - Self::Deploy(_, _, deployment, fee) => { + Self::Deploy(_, _, _, deployment, fee) => { // Check if the ID is the transition ID for the fee. if *id == **fee.id() { // Return the transaction leaf. @@ -48,7 +48,7 @@ impl Transaction { // Error if the function hash was not found. bail!("Function hash not found in deployment transaction"); } - Self::Execute(_, execution, fee) => { + Self::Execute(_, _, execution, fee) => { // Check if the ID is the transition ID for the fee. if let Some(fee) = fee { if *id == **fee.id() { @@ -92,9 +92,19 @@ impl Transaction { pub fn to_tree(&self) -> Result> { match self { // Compute the deployment tree. - Transaction::Deploy(_, _, deployment, fee) => Self::deployment_tree(deployment, Some(fee)), + Transaction::Deploy(_, _, _, deployment, fee) => { + let deployment_tree = Self::deployment_tree(deployment)?; + Self::transaction_tree(deployment_tree, deployment.len(), fee) + } // Compute the execution tree. - Transaction::Execute(_, execution, fee) => Self::execution_tree(execution, fee), + Transaction::Execute(_, _, execution, fee) => { + let execution_tree = Self::execution_tree(execution)?; + if let Some(fee) = fee { + Ok(Transaction::transaction_tree(execution_tree, execution.len(), fee)?) + } else { + Ok(execution_tree) + } + } // Compute the fee tree. Transaction::Fee(_, fee) => Self::fee_tree(fee), } @@ -103,76 +113,68 @@ impl Transaction { impl Transaction { /// Returns the Merkle tree for the given deployment. - pub fn deployment_tree(deployment: &Deployment, fee: Option<&Fee>) -> Result> { + pub fn deployment_tree(deployment: &Deployment) -> Result> { // Ensure the number of leaves is within the Merkle tree size. Self::check_deployment_size(deployment)?; // Retrieve the program. let program = deployment.program(); // Prepare the leaves. - let leaves = program.functions().values().enumerate().map(|(index, function)| { - // Construct the transaction leaf. - Ok(TransactionLeaf::new_deployment( - u16::try_from(index)?, - N::hash_bhp1024(&to_bits_le![program.id(), function.to_bytes_le()?])?, - ) - .to_bits_le()) - }); - // If the fee is present, add it to the leaves. - let leaves = match fee { - Some(fee) => { + let leaves = program + .functions() + .values() + .enumerate() + .map(|(index, function)| { // Construct the transaction leaf. - let leaf = TransactionLeaf::new_fee( - u16::try_from(program.functions().len())?, // The last index. - **fee.transition_id(), + Ok(TransactionLeaf::new_deployment( + u16::try_from(index)?, + N::hash_bhp1024(&to_bits_le![program.id(), function.to_bytes_le()?])?, ) - .to_bits_le(); - // Add the leaf to the leaves. - leaves.chain([Ok(leaf)].into_iter()).collect::>>()? - } - None => leaves.collect::>>()?, - }; + .to_bits_le()) + }) + .collect::>>()?; // Compute the deployment tree. N::merkle_tree_bhp::(&leaves) } /// Returns the Merkle tree for the given execution. - pub fn execution_tree(execution: &Execution, fee: &Option>) -> Result> { - Self::transitions_tree(execution.transitions(), fee) + pub fn execution_tree(execution: &Execution) -> Result> { + Self::transitions_tree(execution.transitions()) } /// Returns the Merkle tree for the given transitions. pub fn transitions_tree<'a>( transitions: impl ExactSizeIterator>, - fee: &Option>, - ) -> Result> { + ) -> Result> { // Retrieve the number of transitions. let num_transitions = transitions.len(); // Ensure the number of leaves is within the Merkle tree size. Self::check_execution_size(num_transitions)?; // Prepare the leaves. - let leaves = transitions.enumerate().map(|(index, transition)| { - // Construct the transaction leaf. - Ok::<_, Error>(TransactionLeaf::new_execution(u16::try_from(index)?, **transition.id()).to_bits_le()) - }); - // If the fee is present, add it to the leaves. - let leaves = match fee { - Some(fee) => { + let leaves = transitions + .enumerate() + .map(|(index, transition)| { // Construct the transaction leaf. - let leaf = TransactionLeaf::new_fee( - u16::try_from(num_transitions)?, // The last index. - **fee.transition_id(), - ) - .to_bits_le(); - // Add the leaf to the leaves. - leaves.chain([Ok(leaf)].into_iter()).collect::, _>>()? - } - None => leaves.collect::, _>>()?, - }; - + Ok::<_, Error>(TransactionLeaf::new_execution(u16::try_from(index)?, **transition.id()).to_bits_le()) + }) + .collect::, _>>()?; // Compute the execution tree. N::merkle_tree_bhp::(&leaves) } + /// Returns the Merkle tree for the given 1. transaction or deployment tree and 2. fee. + pub fn transaction_tree( + mut deployment_or_execution_tree: TransactionTree, + fee_index: usize, + fee: &Fee, + ) -> Result> { + // Construct the transaction leaf. + let leaf = TransactionLeaf::new_fee(u16::try_from(fee_index)?, **fee.transition_id()).to_bits_le(); + // Compute the updated transaction tree. + deployment_or_execution_tree.append(&[leaf])?; + + Ok(deployment_or_execution_tree) + } + /// Returns the Merkle tree for the given fee. pub fn fee_tree(fee: &Fee) -> Result> { // Construct the transaction leaf. diff --git a/ledger/block/src/transaction/mod.rs b/ledger/block/src/transaction/mod.rs index 61e49e6afe..ac052e2498 100644 --- a/ledger/block/src/transaction/mod.rs +++ b/ledger/block/src/transaction/mod.rs @@ -30,16 +30,29 @@ mod string; use crate::Transition; use console::{ network::prelude::*, - program::{Ciphertext, ProgramOwner, Record, TRANSACTION_DEPTH, TransactionLeaf, TransactionPath, TransactionTree}, + program::{ + Ciphertext, + DeploymentTree, + ExecutionTree, + ProgramOwner, + Record, + TRANSACTION_DEPTH, + TransactionLeaf, + TransactionPath, + TransactionTree, + }, types::{Field, Group, U64}, }; +type DeploymentID = Field; +type ExecutionID = Field; + #[derive(Clone, PartialEq, Eq)] pub enum Transaction { /// The deploy transaction publishes an Aleo program to the network. - Deploy(N::TransactionID, ProgramOwner, Box>, Fee), + Deploy(N::TransactionID, DeploymentID, ProgramOwner, Box>, Fee), /// The execute transaction represents a call to an Aleo program. - Execute(N::TransactionID, Execution, Option>), + Execute(N::TransactionID, ExecutionID, Execution, Option>), /// The fee transaction represents a fee paid to the network, used for rejected transactions. Fee(N::TransactionID, Fee), } @@ -49,24 +62,36 @@ impl Transaction { pub fn from_deployment(owner: ProgramOwner, deployment: Deployment, fee: Fee) -> Result { // Ensure the transaction is not empty. ensure!(!deployment.program().functions().is_empty(), "Attempted to create an empty deployment transaction"); - // Compute the transaction ID. - let id = *Self::deployment_tree(&deployment, Some(&fee))?.root(); + // Compute the deployment tree. + let deployment_tree = Self::deployment_tree(&deployment)?; // Compute the deployment ID. - let deployment_id = deployment.to_deployment_id()?; + let deployment_id = *deployment_tree.root(); + // Compute the transaction ID + let transaction_id = *Self::transaction_tree(deployment_tree, deployment.len(), &fee)?.root(); // Ensure the owner signed the correct transaction ID. ensure!(owner.verify(deployment_id), "Attempted to create a deployment transaction with an invalid owner"); // Construct the deployment transaction. - Ok(Self::Deploy(id.into(), owner, Box::new(deployment), fee)) + Ok(Self::Deploy(transaction_id.into(), deployment_id, owner, Box::new(deployment), fee)) } /// Initializes a new execution transaction. pub fn from_execution(execution: Execution, fee: Option>) -> Result { // Ensure the transaction is not empty. ensure!(!execution.is_empty(), "Attempted to create an empty execution transaction"); - // Compute the transaction ID. - let id = *Self::execution_tree(&execution, &fee)?.root(); + // Compute the execution tree. + let execution_tree = Self::execution_tree(&execution)?; + // Compute the execution ID. + let execution_id = *execution_tree.root(); + // Compute the transaction ID + let transaction_id = match &fee { + Some(fee) => { + // Compute the root of the transacton tree. + *Self::transaction_tree(execution_tree, execution.len(), fee)?.root() + } + None => execution_id, + }; // Construct the execution transaction. - Ok(Self::Execute(id.into(), execution, fee)) + Ok(Self::Execute(transaction_id.into(), execution_id, execution, fee)) } /// Initializes a new fee transaction. @@ -106,7 +131,7 @@ impl Transaction { pub fn contains_split(&self) -> bool { match self { // Case 1 - The transaction contains a transition that calls 'credits.aleo/split'. - Transaction::Execute(_, execution, _) => execution.transitions().any(|transition| transition.is_split()), + Transaction::Execute(_, _, execution, _) => execution.transitions().any(|transition| transition.is_split()), // Otherwise, return 'false'. _ => false, } @@ -118,7 +143,7 @@ impl Transaction { #[inline] pub fn owner(&self) -> Option<&ProgramOwner> { match self { - Self::Deploy(_, owner, _, _) => Some(owner), + Self::Deploy(_, _, owner, _, _) => Some(owner), _ => None, } } @@ -127,7 +152,7 @@ impl Transaction { #[inline] pub fn deployment(&self) -> Option<&Deployment> { match self { - Self::Deploy(_, _, deployment, _) => Some(deployment.as_ref()), + Self::Deploy(_, _, _, deployment, _) => Some(deployment.as_ref()), _ => None, } } @@ -136,7 +161,7 @@ impl Transaction { #[inline] pub fn execution(&self) -> Option<&Execution> { match self { - Self::Execute(_, execution, _) => Some(execution), + Self::Execute(_, _, execution, _) => Some(execution), _ => None, } } @@ -186,9 +211,9 @@ impl Transaction { /// Returns the transaction total fee. pub fn fee_amount(&self) -> Result> { match self { - Self::Deploy(_, _, _, fee) => fee.amount(), - Self::Execute(_, _, Some(fee)) => fee.amount(), - Self::Execute(_, _, None) => Ok(U64::zero()), + Self::Deploy(_, _, _, _, fee) => fee.amount(), + Self::Execute(_, _, _, Some(fee)) => fee.amount(), + Self::Execute(_, _, _, None) => Ok(U64::zero()), Self::Fee(_, fee) => fee.amount(), } } @@ -196,9 +221,9 @@ impl Transaction { /// Returns the transaction base fee. pub fn base_fee_amount(&self) -> Result> { match self { - Self::Deploy(_, _, _, fee) => fee.base_amount(), - Self::Execute(_, _, Some(fee)) => fee.base_amount(), - Self::Execute(_, _, None) => Ok(U64::zero()), + Self::Deploy(_, _, _, _, fee) => fee.base_amount(), + Self::Execute(_, _, _, Some(fee)) => fee.base_amount(), + Self::Execute(_, _, _, None) => Ok(U64::zero()), Self::Fee(_, fee) => fee.base_amount(), } } @@ -206,9 +231,9 @@ impl Transaction { /// Returns the transaction priority fee. pub fn priority_fee_amount(&self) -> Result> { match self { - Self::Deploy(_, _, _, fee) => fee.priority_amount(), - Self::Execute(_, _, Some(fee)) => fee.priority_amount(), - Self::Execute(_, _, None) => Ok(U64::zero()), + Self::Deploy(_, _, _, _, fee) => fee.priority_amount(), + Self::Execute(_, _, _, Some(fee)) => fee.priority_amount(), + Self::Execute(_, _, _, None) => Ok(U64::zero()), Self::Fee(_, fee) => fee.priority_amount(), } } @@ -216,8 +241,8 @@ impl Transaction { /// Returns the fee transition. pub fn fee_transition(&self) -> Option> { match self { - Self::Deploy(_, _, _, fee) => Some(fee.clone()), - Self::Execute(_, _, fee) => fee.clone(), + Self::Deploy(_, _, _, _, fee) => Some(fee.clone()), + Self::Execute(_, _, _, fee) => fee.clone(), Self::Fee(_, fee) => Some(fee.clone()), } } @@ -228,9 +253,9 @@ impl Transaction { pub fn contains_transition(&self, transition_id: &N::TransitionID) -> bool { match self { // Check the fee. - Self::Deploy(_, _, _, fee) => fee.id() == transition_id, + Self::Deploy(_, _, _, _, fee) => fee.id() == transition_id, // Check the execution and fee. - Self::Execute(_, execution, fee) => { + Self::Execute(_, _, execution, fee) => { execution.contains_transition(transition_id) || fee.as_ref().map_or(false, |fee| fee.id() == transition_id) } @@ -255,12 +280,12 @@ impl Transaction { pub fn find_transition(&self, transition_id: &N::TransitionID) -> Option<&Transition> { match self { // Check the fee. - Self::Deploy(_, _, _, fee) => match fee.id() == transition_id { + Self::Deploy(_, _, _, _, fee) => match fee.id() == transition_id { true => Some(fee.transition()), false => None, }, // Check the execution and fee. - Self::Execute(_, execution, fee) => execution.get_transition(transition_id).or_else(|| { + Self::Execute(_, _, execution, fee) => execution.get_transition(transition_id).or_else(|| { fee.as_ref().and_then(|fee| match fee.id() == transition_id { true => Some(fee.transition()), false => None, @@ -299,8 +324,8 @@ impl Transaction { /// Returns an iterator over all transitions. pub fn transitions(&self) -> impl '_ + DoubleEndedIterator> { match self { - Self::Deploy(_, _, _, fee) => IterWrap::Deploy(Some(fee.transition()).into_iter()), - Self::Execute(_, execution, fee) => { + Self::Deploy(_, _, _, _, fee) => IterWrap::Deploy(Some(fee.transition()).into_iter()), + Self::Execute(_, _, execution, fee) => { IterWrap::Execute(execution.transitions().chain(fee.as_ref().map(|fee| fee.transition()))) } Self::Fee(_, fee) => IterWrap::Fee(Some(fee.transition()).into_iter()), @@ -366,8 +391,8 @@ impl Transaction { /// Returns a consuming iterator over all transitions. pub fn into_transitions(self) -> impl DoubleEndedIterator> { match self { - Self::Deploy(_, _, _, fee) => IterWrap::Deploy(Some(fee.into_transition()).into_iter()), - Self::Execute(_, execution, fee) => { + Self::Deploy(_, _, _, _, fee) => IterWrap::Deploy(Some(fee.into_transition()).into_iter()), + Self::Execute(_, _, execution, fee) => { IterWrap::Execute(execution.into_transitions().chain(fee.map(|fee| fee.into_transition()))) } Self::Fee(_, fee) => IterWrap::Fee(Some(fee.into_transition()).into_iter()), @@ -470,3 +495,43 @@ pub mod test_helpers { Transaction::from_fee(fee).unwrap() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_transaction_id() -> Result<()> { + let rng = &mut TestRng::default(); + + // Transaction IDs are created using `transaction_tree`. + for expected in [ + crate::transaction::test_helpers::sample_deployment_transaction(true, rng), + crate::transaction::test_helpers::sample_deployment_transaction(false, rng), + crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng), + crate::transaction::test_helpers::sample_execution_transaction_with_fee(false, rng), + ] + .into_iter() + { + match expected { + // Compare against transaction IDs created using `deployment_tree`. + Transaction::Deploy(transaction_id, deployment_id, _, ref deployment, _) => { + let expected_transaction_id = *expected.clone().to_tree()?.root(); + assert_eq!(expected_transaction_id, *transaction_id); + let expected_deployment_id = *Transaction::deployment_tree(deployment)?.root(); + assert_eq!(expected_deployment_id, deployment_id); + } + // Compare against transaction IDs created using `execution_tree`. + Transaction::Execute(transaction_id, execution_id, ref execution, _) => { + let expected_transaction_id = *expected.clone().to_tree()?.root(); + assert_eq!(expected_transaction_id, *transaction_id); + let expected_execution_id = *Transaction::execution_tree(execution)?.root(); + assert_eq!(expected_execution_id, execution_id); + } + _ => panic!("Unexpected test case."), + }; + } + + Ok(()) + } +} diff --git a/ledger/block/src/transaction/serialize.rs b/ledger/block/src/transaction/serialize.rs index 78d763dd0d..a666daec66 100644 --- a/ledger/block/src/transaction/serialize.rs +++ b/ledger/block/src/transaction/serialize.rs @@ -19,8 +19,9 @@ impl Serialize for Transaction { /// Serializes the transaction to a JSON-string or buffer. fn serialize(&self, serializer: S) -> Result { match serializer.is_human_readable() { + // We don't write the deployment or execution id, which are recomputed when creating the Transaction. true => match self { - Self::Deploy(id, owner, deployment, fee) => { + Self::Deploy(id, _, owner, deployment, fee) => { let mut transaction = serializer.serialize_struct("Transaction", 5)?; transaction.serialize_field("type", "deploy")?; transaction.serialize_field("id", &id)?; @@ -29,7 +30,7 @@ impl Serialize for Transaction { transaction.serialize_field("fee", &fee)?; transaction.end() } - Self::Execute(id, execution, fee) => { + Self::Execute(id, _, execution, fee) => { let mut transaction = serializer.serialize_struct("Transaction", 3 + fee.is_some() as usize)?; transaction.serialize_field("type", "execute")?; transaction.serialize_field("id", &id)?; diff --git a/ledger/block/src/transactions/confirmed/mod.rs b/ledger/block/src/transactions/confirmed/mod.rs index 9593e2ace5..9130f84362 100644 --- a/ledger/block/src/transactions/confirmed/mod.rs +++ b/ledger/block/src/transactions/confirmed/mod.rs @@ -45,7 +45,7 @@ impl ConfirmedTransaction { ) -> Result { // Retrieve the program and fee from the deployment transaction, and ensure the transaction is a deploy transaction. let (program, fee) = match &transaction { - Transaction::Deploy(_, _, deployment, fee) => (deployment.program(), fee), + Transaction::Deploy(_, _, _, deployment, fee) => (deployment.program(), fee), Transaction::Execute(..) | Transaction::Fee(..) => { bail!("Transaction '{}' is not a deploy transaction", transaction.id()) } diff --git a/ledger/block/src/transactions/rejected/mod.rs b/ledger/block/src/transactions/rejected/mod.rs index 655ee40f67..51a9e102a9 100644 --- a/ledger/block/src/transactions/rejected/mod.rs +++ b/ledger/block/src/transactions/rejected/mod.rs @@ -85,9 +85,14 @@ impl Rejected { /// When a transaction is rejected, its fee transition is used to construct the confirmed transaction ID, /// changing the original transaction ID. pub fn to_unconfirmed_id(&self, fee: &Option>) -> Result> { - match self { - Self::Deployment(_, deployment) => Ok(*Transaction::deployment_tree(deployment, fee.as_ref())?.root()), - Self::Execution(execution) => Ok(*Transaction::execution_tree(execution, fee)?.root()), + let (tree, fee_index) = match self { + Self::Deployment(_, deployment) => (Transaction::deployment_tree(deployment)?, deployment.len()), + Self::Execution(execution) => (Transaction::execution_tree(execution)?, execution.len()), + }; + if let Some(fee) = fee { + Ok(*Transaction::transaction_tree(tree, fee_index, fee)?.root()) + } else { + Ok(*tree.root()) } } } @@ -103,7 +108,7 @@ pub mod test_helpers { pub(crate) fn sample_rejected_deployment(is_fee_private: bool, rng: &mut TestRng) -> Rejected { // Sample a deploy transaction. let deployment = match crate::transaction::test_helpers::sample_deployment_transaction(is_fee_private, rng) { - Transaction::Deploy(_, _, deployment, _) => (*deployment).clone(), + Transaction::Deploy(_, _, _, deployment, _) => (*deployment).clone(), _ => unreachable!(), }; @@ -121,7 +126,7 @@ pub mod test_helpers { // Sample an execute transaction. let execution = match crate::transaction::test_helpers::sample_execution_transaction_with_fee(is_fee_private, rng) { - Transaction::Execute(_, execution, _) => execution, + Transaction::Execute(_, _, execution, _) => execution, _ => unreachable!(), }; diff --git a/ledger/block/src/transition/mod.rs b/ledger/block/src/transition/mod.rs index c72e61b650..c205b6a445 100644 --- a/ledger/block/src/transition/mod.rs +++ b/ledger/block/src/transition/mod.rs @@ -495,7 +495,7 @@ pub mod test_helpers { /// Samples a random transition. pub(crate) fn sample_transition(rng: &mut TestRng) -> Transition { - if let Transaction::Execute(_, execution, _) = + if let Transaction::Execute(_, _, execution, _) = crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng) { execution.into_transitions().next().unwrap() diff --git a/ledger/src/tests.rs b/ledger/src/tests.rs index d3aacbd33c..3d2ab59844 100644 --- a/ledger/src/tests.rs +++ b/ledger/src/tests.rs @@ -577,7 +577,7 @@ finalize failed_assert: assert_eq!(next_block.transactions().len(), 1); let confirmed_transaction = next_block.transactions().iter().next().unwrap(); assert!(confirmed_transaction.is_rejected()); - if let Transaction::Execute(_, execution, fee) = failed_assert_transaction { + if let Transaction::Execute(_, _, execution, fee) = failed_assert_transaction { let fee_transaction = Transaction::from_fee(fee.unwrap()).unwrap(); let expected_confirmed_transaction = ConfirmedTransaction::RejectedExecute(0, fee_transaction, Rejected::new_execution(execution), vec![]); diff --git a/ledger/store/src/transaction/deployment.rs b/ledger/store/src/transaction/deployment.rs index a0c3d685a7..16df88efd0 100644 --- a/ledger/store/src/transaction/deployment.rs +++ b/ledger/store/src/transaction/deployment.rs @@ -166,7 +166,7 @@ pub trait DeploymentStorage: Clone + Send + Sync { fn insert(&self, transaction: &Transaction) -> Result<()> { // Ensure the transaction is a deployment. let (transaction_id, owner, deployment, fee) = match transaction { - Transaction::Deploy(transaction_id, owner, deployment, fee) => (transaction_id, owner, deployment, fee), + Transaction::Deploy(transaction_id, _, owner, deployment, fee) => (transaction_id, owner, deployment, fee), Transaction::Execute(..) => bail!("Attempted to insert an execute transaction into deployment storage."), Transaction::Fee(..) => bail!("Attempted to insert fee transaction into deployment storage."), }; @@ -725,7 +725,7 @@ mod tests { for transaction in transactions { let transaction_id = transaction.id(); let program_id = match transaction { - Transaction::Deploy(_, _, ref deployment, _) => *deployment.program_id(), + Transaction::Deploy(_, _, _, ref deployment, _) => *deployment.program_id(), _ => panic!("Incorrect transaction type"), }; diff --git a/ledger/store/src/transaction/execution.rs b/ledger/store/src/transaction/execution.rs index 089372b788..04ccaaa7d6 100644 --- a/ledger/store/src/transaction/execution.rs +++ b/ledger/store/src/transaction/execution.rs @@ -124,7 +124,7 @@ pub trait ExecutionStorage: Clone + Send + Sync { // Ensure the transaction is a execution. let (transaction_id, execution, fee) = match transaction { Transaction::Deploy(..) => bail!("Attempted to insert a deploy transaction into execution storage."), - Transaction::Execute(transaction_id, execution, fee) => (transaction_id, execution, fee), + Transaction::Execute(transaction_id, _, execution, fee) => (transaction_id, execution, fee), Transaction::Fee(..) => bail!("Attempted to insert a fee transaction into execution storage."), }; diff --git a/ledger/test-helpers/src/lib.rs b/ledger/test-helpers/src/lib.rs index 7d789c6fdd..abf5babd38 100644 --- a/ledger/test-helpers/src/lib.rs +++ b/ledger/test-helpers/src/lib.rs @@ -164,7 +164,7 @@ function compute: pub fn sample_rejected_deployment(is_fee_private: bool, rng: &mut TestRng) -> Rejected { // Sample a deploy transaction. let deployment = match crate::sample_deployment_transaction(is_fee_private, rng) { - Transaction::Deploy(_, _, deployment, _) => (*deployment).clone(), + Transaction::Deploy(_, _, _, deployment, _) => (*deployment).clone(), _ => unreachable!(), }; @@ -186,14 +186,14 @@ pub fn sample_execution(rng: &mut TestRng) -> Execution { // Retrieve a transaction. let transaction = block.transactions().iter().next().unwrap().deref().clone(); // Retrieve the execution. - if let Transaction::Execute(_, execution, _) = transaction { execution } else { unreachable!() } + if let Transaction::Execute(_, _, execution, _) = transaction { execution } else { unreachable!() } } /// Samples a rejected execution. pub fn sample_rejected_execution(is_fee_private: bool, rng: &mut TestRng) -> Rejected { // Sample an execute transaction. let execution = match crate::sample_execution_transaction_with_fee(is_fee_private, rng) { - Transaction::Execute(_, execution, _) => execution, + Transaction::Execute(_, _, execution, _) => execution, _ => unreachable!(), }; diff --git a/synthesizer/process/src/stack/authorization/mod.rs b/synthesizer/process/src/stack/authorization/mod.rs index b7aa4b9669..77eef1c9e5 100644 --- a/synthesizer/process/src/stack/authorization/mod.rs +++ b/synthesizer/process/src/stack/authorization/mod.rs @@ -180,7 +180,7 @@ impl Authorization { if transitions.is_empty() { bail!("Cannot compute the execution ID for an empty authorization."); } - Ok(*Transaction::transitions_tree(transitions.values(), &None)?.root()) + Ok(*Transaction::transitions_tree(transitions.values())?.root()) } } diff --git a/synthesizer/process/src/verify_fee.rs b/synthesizer/process/src/verify_fee.rs index 02d44de3e5..f3ee3e8dfb 100644 --- a/synthesizer/process/src/verify_fee.rs +++ b/synthesizer/process/src/verify_fee.rs @@ -251,13 +251,13 @@ mod tests { for transaction in transactions { match transaction { - Transaction::Deploy(_, _, deployment, fee) => { + Transaction::Deploy(_, _, _, deployment, fee) => { // Compute the deployment ID. let deployment_id = deployment.to_deployment_id().unwrap(); // Verify the fee. process.verify_fee(&fee, deployment_id).unwrap(); } - Transaction::Execute(_, execution, fee) => { + Transaction::Execute(_, _, execution, fee) => { // Compute the execution ID. let execution_id = execution.to_execution_id().unwrap(); // Verify the fee. diff --git a/synthesizer/src/vm/execute.rs b/synthesizer/src/vm/execute.rs index 42d831d10c..145532aaf5 100644 --- a/synthesizer/src/vm/execute.rs +++ b/synthesizer/src/vm/execute.rs @@ -284,8 +284,8 @@ mod tests { assert_eq!(2914, transaction_size_in_bytes, "Update me if serialization has changed"); // Assert the size of the execution. - assert!(matches!(transaction, Transaction::Execute(_, _, _))); - if let Transaction::Execute(_, execution, _) = &transaction { + assert!(matches!(transaction, Transaction::Execute(_, _, _, _))); + if let Transaction::Execute(_, _, execution, _) = &transaction { let execution_size_in_bytes = execution.to_bytes_le().unwrap().len(); assert_eq!(1463, execution_size_in_bytes, "Update me if serialization has changed"); } @@ -325,8 +325,8 @@ mod tests { assert_eq!(2970, transaction_size_in_bytes, "Update me if serialization has changed"); // Assert the size of the execution. - assert!(matches!(transaction, Transaction::Execute(_, _, _))); - if let Transaction::Execute(_, execution, _) = &transaction { + assert!(matches!(transaction, Transaction::Execute(_, _, _, _))); + if let Transaction::Execute(_, _, execution, _) = &transaction { let execution_size_in_bytes = execution.to_bytes_le().unwrap().len(); assert_eq!(1519, execution_size_in_bytes, "Update me if serialization has changed"); } @@ -531,8 +531,8 @@ finalize test: assert_eq!(2867, transaction_size_in_bytes, "Update me if serialization has changed"); // Assert the size of the execution. - assert!(matches!(transaction, Transaction::Execute(_, _, _))); - if let Transaction::Execute(_, execution, _) = &transaction { + assert!(matches!(transaction, Transaction::Execute(_, _, _, _))); + if let Transaction::Execute(_, _, execution, _) = &transaction { let execution_size_in_bytes = execution.to_bytes_le().unwrap().len(); assert_eq!(1416, execution_size_in_bytes, "Update me if serialization has changed"); } @@ -570,8 +570,8 @@ finalize test: assert_eq!(3693, transaction_size_in_bytes, "Update me if serialization has changed"); // Assert the size of the execution. - assert!(matches!(transaction, Transaction::Execute(_, _, _))); - if let Transaction::Execute(_, execution, _) = &transaction { + assert!(matches!(transaction, Transaction::Execute(_, _, _, _))); + if let Transaction::Execute(_, _, execution, _) = &transaction { let execution_size_in_bytes = execution.to_bytes_le().unwrap().len(); assert_eq!(2242, execution_size_in_bytes, "Update me if serialization has changed"); } @@ -604,8 +604,8 @@ finalize test: assert_eq!(2871, transaction_size_in_bytes, "Update me if serialization has changed"); // Assert the size of the execution. - assert!(matches!(transaction, Transaction::Execute(_, _, _))); - if let Transaction::Execute(_, execution, _) = &transaction { + assert!(matches!(transaction, Transaction::Execute(_, _, _, _))); + if let Transaction::Execute(_, _, execution, _) = &transaction { let execution_size_in_bytes = execution.to_bytes_le().unwrap().len(); assert_eq!(1420, execution_size_in_bytes, "Update me if serialization has changed"); } @@ -638,8 +638,8 @@ finalize test: assert_eq!(2891, transaction_size_in_bytes, "Update me if serialization has changed"); // Assert the size of the execution. - assert!(matches!(transaction, Transaction::Execute(_, _, _))); - if let Transaction::Execute(_, execution, _) = &transaction { + assert!(matches!(transaction, Transaction::Execute(_, _, _, _))); + if let Transaction::Execute(_, _, execution, _) = &transaction { let execution_size_in_bytes = execution.to_bytes_le().unwrap().len(); assert_eq!(1440, execution_size_in_bytes, "Update me if serialization has changed"); } @@ -673,8 +673,8 @@ finalize test: assert_eq!(3538, transaction_size_in_bytes, "Update me if serialization has changed"); // Assert the size of the execution. - assert!(matches!(transaction, Transaction::Execute(_, _, _))); - if let Transaction::Execute(_, execution, _) = &transaction { + assert!(matches!(transaction, Transaction::Execute(_, _, _, _))); + if let Transaction::Execute(_, _, execution, _) = &transaction { let execution_size_in_bytes = execution.to_bytes_le().unwrap().len(); assert_eq!(2087, execution_size_in_bytes, "Update me if serialization has changed"); } @@ -711,8 +711,8 @@ finalize test: assert_eq!(2166, transaction_size_in_bytes, "Update me if serialization has changed"); // Assert the size of the execution. - assert!(matches!(transaction, Transaction::Execute(_, _, _))); - if let Transaction::Execute(_, execution, _) = &transaction { + assert!(matches!(transaction, Transaction::Execute(_, _, _, _))); + if let Transaction::Execute(_, _, execution, _) = &transaction { let execution_size_in_bytes = execution.to_bytes_le().unwrap().len(); assert_eq!(2131, execution_size_in_bytes, "Update me if serialization has changed"); } @@ -869,7 +869,7 @@ finalize test: vm.add_next_block(&next_block).unwrap(); // Execute the parent program. - let Transaction::Execute(_, execution, _) = vm + let Transaction::Execute(_, _, execution, _) = vm .execute(&caller_private_key, ("parent.aleo", "test"), Vec::>::new().iter(), None, 0, None, rng) .unwrap() else { @@ -998,7 +998,7 @@ finalize test: } // Execute the program. - let Transaction::Execute(_, execution, _) = vm + let Transaction::Execute(_, _, execution, _) = vm .execute( &caller_private_key, (format!("test_{}.aleo", Transaction::::MAX_TRANSITIONS - 1), "test"), diff --git a/synthesizer/src/vm/finalize.rs b/synthesizer/src/vm/finalize.rs index dda3f3137b..7835ba3e08 100644 --- a/synthesizer/src/vm/finalize.rs +++ b/synthesizer/src/vm/finalize.rs @@ -331,7 +331,7 @@ impl> VM { let outcome = match transaction { // The finalize operation here involves appending the 'stack', // and adding the program to the finalize tree. - Transaction::Deploy(_, program_owner, deployment, fee) => { + Transaction::Deploy(_, _, program_owner, deployment, fee) => { // Define the closure for processing a rejected deployment. let process_rejected_deployment = |fee: &Fee, @@ -391,7 +391,7 @@ impl> VM { } // The finalize operation here involves calling 'update_key_value', // and update the respective leaves of the finalize tree. - Transaction::Execute(_, execution, fee) => { + Transaction::Execute(_, _, execution, fee) => { // Determine if the transaction is safe for execution, and proceed to execute it. match Self::prepare_for_execution(state, store, execution) .and_then(|_| process.finalize_execution(state, store, execution, fee.as_ref())) @@ -453,7 +453,7 @@ impl> VM { // Add the transition public keys to the set of produced transition public keys. tpks.extend(confirmed_transaction.transaction().transition_public_keys()); // Add any public deployment payer to the set of deployment payers. - if let Transaction::Deploy(_, _, _, fee) = confirmed_transaction.transaction() { + if let Transaction::Deploy(_, _, _, _, fee) = confirmed_transaction.transaction() { fee.payer().map(|payer| deployment_payers.insert(payer)); } // Store the confirmed transaction. @@ -610,7 +610,7 @@ impl> VM { ConfirmedTransaction::AcceptedDeploy(_, transaction, finalize) => { // Extract the deployment and fee from the transaction. let (deployment, fee) = match transaction { - Transaction::Deploy(_, _, deployment, fee) => (deployment, fee), + Transaction::Deploy(_, _, _, deployment, fee) => (deployment, fee), // Note: This will abort the entire atomic batch. _ => return Err("Expected deploy transaction".to_string()), }; @@ -637,7 +637,7 @@ impl> VM { ConfirmedTransaction::AcceptedExecute(_, transaction, finalize) => { // Extract the execution and fee from the transaction. let (execution, fee) = match transaction { - Transaction::Execute(_, execution, fee) => (execution, fee), + Transaction::Execute(_, _, execution, fee) => (execution, fee), // Note: This will abort the entire atomic batch. _ => return Err("Expected execute transaction".to_string()), }; @@ -830,7 +830,7 @@ impl> VM { } // If the transaction is a deployment, ensure that it is not another deployment in the block from the same public fee payer. - if let Transaction::Deploy(_, _, _, fee) = transaction { + if let Transaction::Deploy(_, _, _, _, fee) = transaction { // If any public deployment payer has already deployed in this block, abort the transaction. if let Some(payer) = fee.payer() { if deployment_payers.contains(&payer) { @@ -901,7 +901,7 @@ impl> VM { // Add the transition public keys to the set of produced transition public keys. tpks.extend(transaction.transition_public_keys()); // Add any public deployment payer to the set of deployment payers. - if let Transaction::Deploy(_, _, _, fee) = transaction { + if let Transaction::Deploy(_, _, _, _, fee) = transaction { fee.payer().map(|payer| deployment_payers.insert(payer)); } @@ -1660,7 +1660,7 @@ finalize transfer_public: finalize: &[FinalizeOperation], ) -> ConfirmedTransaction { match transaction { - Transaction::Execute(_, execution, fee) => ConfirmedTransaction::RejectedExecute( + Transaction::Execute(_, _, execution, fee) => ConfirmedTransaction::RejectedExecute( index, Transaction::from_fee(fee.clone().unwrap()).unwrap(), Rejected::new_execution(execution.clone()), @@ -2410,7 +2410,7 @@ function ped_hash: // Ensure that the transaction is rejected. assert_eq!(confirmed_transactions.len(), 1); assert!(transaction.is_execute()); - if let Transaction::Execute(_, execution, fee) = transaction { + if let Transaction::Execute(_, _, execution, fee) = transaction { let fee_transaction = Transaction::from_fee(fee.unwrap()).unwrap(); let expected_confirmed_transaction = ConfirmedTransaction::RejectedExecute( 0, diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index aed27fb2bc..a974134c4d 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -1463,7 +1463,7 @@ function do: let transaction = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); // Destructure the deployment transaction. - let Transaction::Deploy(_, program_owner, deployment, fee) = transaction else { + let Transaction::Deploy(_, _, program_owner, deployment, fee) = transaction else { panic!("Expected a deployment transaction"); }; @@ -1527,7 +1527,7 @@ function do: let transaction = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); // Destructure the deployment transaction. - let Transaction::Deploy(txid, program_owner, deployment, fee) = transaction else { + let Transaction::Deploy(txid, _, program_owner, deployment, fee) = transaction else { panic!("Expected a deployment transaction"); }; @@ -1543,7 +1543,9 @@ function do: // Create a new deployment transaction with the underreported verifying keys. let adjusted_deployment = Deployment::new(deployment.edition(), deployment.program().clone(), vks_with_underreport).unwrap(); - let adjusted_transaction = Transaction::Deploy(txid, program_owner, Box::new(adjusted_deployment), fee); + let deployment_id = adjusted_deployment.to_deployment_id().unwrap(); + let adjusted_transaction = + Transaction::Deploy(txid, deployment_id, program_owner, Box::new(adjusted_deployment), fee); // Verify the deployment transaction. It should error when enforcing the first constraint over the vk limit. let result = vm.check_transaction(&adjusted_transaction, None, rng); @@ -1602,7 +1604,7 @@ function do: let transaction = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); // Destructure the deployment transaction. - let Transaction::Deploy(txid, program_owner, deployment, fee) = transaction else { + let Transaction::Deploy(txid, _, program_owner, deployment, fee) = transaction else { panic!("Expected a deployment transaction"); }; @@ -1616,7 +1618,9 @@ function do: // Create a new deployment transaction with the underreported verifying keys. let adjusted_deployment = Deployment::new(deployment.edition(), deployment.program().clone(), vks_with_underreport).unwrap(); - let adjusted_transaction = Transaction::Deploy(txid, program_owner, Box::new(adjusted_deployment), fee); + let deployment_id = adjusted_deployment.to_deployment_id().unwrap(); + let adjusted_transaction = + Transaction::Deploy(txid, deployment_id, program_owner, Box::new(adjusted_deployment), fee); // Verify the deployment transaction. It should error when synthesizing the first variable over the vk limit. let result = vm.check_transaction(&adjusted_transaction, None, rng); diff --git a/synthesizer/src/vm/verify.rs b/synthesizer/src/vm/verify.rs index 478350c748..4d5acc0d27 100644 --- a/synthesizer/src/vm/verify.rs +++ b/synthesizer/src/vm/verify.rs @@ -90,6 +90,8 @@ impl> VM { } // Compute the Merkle root of the transaction. + // Debug-mode only, as the `Transaction` constructor recomputes the transaction ID at initialization. + #[cfg(debug_assertions)] match transaction.to_root() { // Ensure the transaction ID is correct. Ok(root) if *transaction.id() != root => bail!("Incorrect transaction ID ({})", transaction.id()), @@ -144,13 +146,9 @@ impl> VM { // Next, verify the deployment or execution. match transaction { - Transaction::Deploy(id, owner, deployment, _) => { - // Compute the deployment ID. - let Ok(deployment_id) = deployment.to_deployment_id() else { - bail!("Failed to compute the Merkle root for a deployment transaction '{id}'") - }; + Transaction::Deploy(id, deployment_id, owner, deployment, _) => { // Verify the signature corresponds to the transaction ID. - ensure!(owner.verify(deployment_id), "Invalid owner signature for deployment transaction '{id}'"); + ensure!(owner.verify(*deployment_id), "Invalid owner signature for deployment transaction '{id}'"); // Ensure the edition is correct. if deployment.edition() != N::EDITION { bail!("Invalid deployment transaction '{id}' - expected edition {}", N::EDITION) @@ -172,13 +170,9 @@ impl> VM { } } } - Transaction::Execute(id, execution, _) => { - // Compute the execution ID. - let Ok(execution_id) = execution.to_execution_id() else { - bail!("Failed to compute the Merkle root for an execution transaction '{id}'") - }; + Transaction::Execute(id, execution_id, execution, _) => { // Ensure the execution was not previously rejected (replay attack prevention). - if self.block_store().contains_rejected_deployment_or_execution_id(&execution_id)? { + if self.block_store().contains_rejected_deployment_or_execution_id(execution_id)? { bail!("Transaction '{id}' contains a previously rejected execution") } // Verify the execution. @@ -209,13 +203,9 @@ impl> VM { is_partially_verified: bool, ) -> Result<()> { match transaction { - Transaction::Deploy(id, _, deployment, fee) => { + Transaction::Deploy(id, deployment_id, _, deployment, fee) => { // Ensure the rejected ID is not present. ensure!(rejected_id.is_none(), "Transaction '{id}' should not have a rejected ID (deployment)"); - // Compute the deployment ID. - let Ok(deployment_id) = deployment.to_deployment_id() else { - bail!("Failed to compute the Merkle root for deployment transaction '{id}'") - }; // Compute the minimum deployment cost. let (cost, _) = deployment_cost(deployment)?; // Ensure the fee is sufficient to cover the cost. @@ -223,15 +213,11 @@ impl> VM { bail!("Transaction '{id}' has an insufficient base fee (deployment) - requires {cost} microcredits") } // Verify the fee. - self.check_fee_internal(fee, deployment_id, is_partially_verified)?; + self.check_fee_internal(fee, *deployment_id, is_partially_verified)?; } - Transaction::Execute(id, execution, fee) => { + Transaction::Execute(id, execution_id, execution, fee) => { // Ensure the rejected ID is not present. ensure!(rejected_id.is_none(), "Transaction '{id}' should not have a rejected ID (execution)"); - // Compute the execution ID. - let Ok(execution_id) = execution.to_execution_id() else { - bail!("Failed to compute the Merkle root for execution transaction '{id}'") - }; // If the transaction contains only 1 transition, and the transition is a split, then the fee can be skipped. let is_fee_required = !(execution.len() == 1 && transaction.contains_split()); // Verify the fee. @@ -258,7 +244,7 @@ impl> VM { ensure!(*fee.base_amount()? == 0, "Transaction '{id}' has a non-zero base fee (execution)"); } // Verify the fee. - self.check_fee_internal(fee, execution_id, is_partially_verified)?; + self.check_fee_internal(fee, *execution_id, is_partially_verified)?; } else { // Ensure the fee can be safely skipped. ensure!(!is_fee_required, "Transaction '{id}' is missing a fee (execution)"); @@ -476,7 +462,7 @@ mod tests { for transaction in transactions { match transaction { - Transaction::Execute(_, execution, _) => { + Transaction::Execute(_, _, execution, _) => { // Ensure the proof exists. assert!(execution.proof().is_some()); // Verify the execution. @@ -517,7 +503,7 @@ mod tests { for transaction in transactions { match transaction { - Transaction::Execute(_, execution, Some(fee)) => { + Transaction::Execute(_, _, execution, Some(fee)) => { let execution_id = execution.to_execution_id().unwrap(); // Ensure the proof exists.