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..7c9d62f929 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 diff --git a/ledger/block/src/transaction/execution/mod.rs b/ledger/block/src/transaction/execution/mod.rs index 36139105c5..c9120622b0 100644 --- a/ledger/block/src/transaction/execution/mod.rs +++ b/ledger/block/src/transaction/execution/mod.rs @@ -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..79870d3f82 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,9 @@ 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) => Self::deployment_tree(deployment, Some(fee)), // Compute the execution tree. - Transaction::Execute(_, execution, fee) => Self::execution_tree(execution, fee), + Transaction::Execute(_, _, execution, fee) => Self::execution_tree(execution, fee), // Compute the fee tree. Transaction::Fee(_, fee) => Self::fee_tree(fee), } @@ -173,6 +173,20 @@ impl Transaction { N::merkle_tree_bhp::(&leaves) } + /// Returns the Merkle tree for the given execution tree and fee. + pub fn transaction_tree( + mut 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 transaction tree. + execution_tree.append(&[leaf])?; + + Ok(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..39567ed5fd 100644 --- a/ledger/block/src/transaction/mod.rs +++ b/ledger/block/src/transaction/mod.rs @@ -34,12 +34,15 @@ use console::{ 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 +52,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, None)?; // 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, &None)?; + // 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 +121,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 +133,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 +142,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 +151,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 +201,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 +211,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 +221,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 +231,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 +243,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 +270,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 +314,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 +381,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 +485,39 @@ 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(_, id, _, deployment, fee) => { + let computed_id = *Transaction::deployment_tree(&deployment, Some(&fee))?.root(); + assert_eq!(computed_id, id); + } + // Compare against transaction IDs created using `execution_tree`. + Transaction::Execute(_, id, execution, fee) => { + let computed_id = *Transaction::execution_tree(&execution, &fee)?.root(); + assert_eq!(computed_id, 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..e3da04c48b 100644 --- a/ledger/block/src/transactions/rejected/mod.rs +++ b/ledger/block/src/transactions/rejected/mod.rs @@ -103,7 +103,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 +121,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 cc5989c0e5..e24ebf7a08 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/verify_fee.rs b/synthesizer/process/src/verify_fee.rs index e9ec997a6b..666eaf013b 100644 --- a/synthesizer/process/src/verify_fee.rs +++ b/synthesizer/process/src/verify_fee.rs @@ -236,13 +236,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 1b6d54e5f4..b5f82e1ad5 100644 --- a/synthesizer/src/vm/execute.rs +++ b/synthesizer/src/vm/execute.rs @@ -279,8 +279,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"); } @@ -320,8 +320,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"); } @@ -385,8 +385,8 @@ mod tests { 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"); } @@ -424,8 +424,8 @@ mod tests { 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"); } @@ -458,8 +458,8 @@ mod tests { 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"); } @@ -492,8 +492,8 @@ mod tests { 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"); } @@ -527,8 +527,8 @@ mod tests { 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"); } @@ -565,8 +565,8 @@ mod tests { 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"); } @@ -723,7 +723,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 { @@ -851,7 +851,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 2cbef6359a..4677269bc6 100644 --- a/synthesizer/src/vm/finalize.rs +++ b/synthesizer/src/vm/finalize.rs @@ -319,7 +319,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, @@ -379,7 +379,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(store, execution) .and_then(|_| process.finalize_execution(state, store, execution, fee.as_ref())) @@ -438,7 +438,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. @@ -593,7 +593,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()), }; @@ -620,7 +620,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()), }; @@ -813,7 +813,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) { @@ -884,7 +884,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)); } @@ -1625,7 +1625,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()), @@ -2123,7 +2123,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 0be0f85440..34a84fb880 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -1424,7 +1424,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"); }; @@ -1488,7 +1488,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"); }; @@ -1504,7 +1504,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); @@ -1563,7 +1565,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"); }; @@ -1577,7 +1579,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 5a2a2d3617..6ebf886cb4 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. @@ -204,13 +198,9 @@ impl> VM { #[inline] pub fn check_fee(&self, transaction: &Transaction, rejected_id: Option>) -> 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. @@ -218,15 +208,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)?; + self.check_fee_internal(fee, *deployment_id)?; } - 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. @@ -246,7 +232,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)?; + self.check_fee_internal(fee, *execution_id)?; } else { // Ensure the fee can be safely skipped. ensure!(!is_fee_required, "Transaction '{id}' is missing a fee (execution)"); @@ -446,7 +432,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. @@ -476,7 +462,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.