diff --git a/synthesizer/src/vm/verify.rs b/synthesizer/src/vm/verify.rs index ab45e68444..478350c748 100644 --- a/synthesizer/src/vm/verify.rs +++ b/synthesizer/src/vm/verify.rs @@ -132,9 +132,6 @@ impl> VM { lap!(timer, "Check for duplicate elements"); - // First, verify the fee. - self.check_fee(transaction, rejected_id)?; - // Construct the transaction checksum. let checksum = Data::>::Buffer(transaction.to_bytes_le()?.into()).to_checksum::()?; @@ -142,6 +139,9 @@ impl> VM { let is_partially_verified = self.partially_verified_transactions.read().peek(&(transaction.id())) == Some(&checksum); + // Verify the fee. + self.check_fee(transaction, rejected_id, is_partially_verified)?; + // Next, verify the deployment or execution. match transaction { Transaction::Deploy(id, owner, deployment, _) => { @@ -202,7 +202,12 @@ impl> VM { /// Verifies the `fee` in the given transaction. On failure, returns an error. #[inline] - pub fn check_fee(&self, transaction: &Transaction, rejected_id: Option>) -> Result<()> { + pub fn check_fee( + &self, + transaction: &Transaction, + rejected_id: Option>, + is_partially_verified: bool, + ) -> Result<()> { match transaction { Transaction::Deploy(id, _, deployment, fee) => { // Ensure the rejected ID is not present. @@ -218,7 +223,7 @@ 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, is_partially_verified)?; } Transaction::Execute(id, execution, fee) => { // Ensure the rejected ID is not present. @@ -253,7 +258,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, is_partially_verified)?; } else { // Ensure the fee can be safely skipped. ensure!(!is_fee_required, "Transaction '{id}' is missing a fee (execution)"); @@ -265,7 +270,7 @@ impl> VM { Transaction::Fee(id, fee) => { // Verify the fee. match rejected_id { - Some(rejected_id) => self.check_fee_internal(fee, rejected_id)?, + Some(rejected_id) => self.check_fee_internal(fee, rejected_id, is_partially_verified)?, None => bail!("Transaction '{id}' is missing a rejected ID (fee)"), } } @@ -339,15 +344,23 @@ impl> VM { /// Note: This is an internal check only. To ensure all components of the fee are checked, /// use `VM::check_fee` instead. #[inline] - fn check_fee_internal(&self, fee: &Fee, deployment_or_execution_id: Field) -> Result<()> { + fn check_fee_internal( + &self, + fee: &Fee, + deployment_or_execution_id: Field, + is_partially_verified: bool, + ) -> Result<()> { let timer = timer!("VM::check_fee"); // Ensure the fee does not exceed the limit. let fee_amount = fee.amount()?; ensure!(*fee_amount <= N::MAX_FEE, "Fee verification failed: fee exceeds the maximum limit"); - // Verify the fee. - let verification = self.process.read().verify_fee(fee, deployment_or_execution_id); + // Verify the fee, if it has not been partially-verified before. + let verification = match is_partially_verified { + true => Ok(()), + false => self.process.read().verify_fee(fee, deployment_or_execution_id), + }; lap!(timer, "Verify the fee"); // TODO (howardwu): This check is technically insufficient. Consider moving this upstream @@ -408,16 +421,22 @@ mod tests { let deployment_transaction = crate::vm::test_helpers::sample_deployment_transaction(rng); // Ensure the transaction verifies. vm.check_transaction(&deployment_transaction, None, rng).unwrap(); + // Ensure the partially_verified_transactions cache is updated. + assert!(vm.partially_verified_transactions.read().peek(&deployment_transaction.id()).is_some()); // Fetch an execution transaction. let execution_transaction = crate::vm::test_helpers::sample_execution_transaction_with_private_fee(rng); // Ensure the transaction verifies. vm.check_transaction(&execution_transaction, None, rng).unwrap(); + // Ensure the partially_verified_transactions cache is updated. + assert!(vm.partially_verified_transactions.read().peek(&execution_transaction.id()).is_some()); // Fetch an execution transaction. let execution_transaction = crate::vm::test_helpers::sample_execution_transaction_with_public_fee(rng); // Ensure the transaction verifies. vm.check_transaction(&execution_transaction, None, rng).unwrap(); + // Ensure the partially_verified_transactions cache is updated. + assert!(vm.partially_verified_transactions.read().peek(&execution_transaction.id()).is_some()); } #[test] @@ -433,11 +452,15 @@ mod tests { // Ensure the deployment is valid. vm.check_deployment_internal(&deployment, rng).unwrap(); + // Ensure the partially_verified_transactions cache is not updated. + assert!(vm.partially_verified_transactions.read().peek(&deployment.to_deployment_id().unwrap()).is_none()); // Ensure that deserialization doesn't break the transaction verification. let serialized_deployment = deployment.to_string(); let deployment_transaction: Deployment = serde_json::from_str(&serialized_deployment).unwrap(); vm.check_deployment_internal(&deployment_transaction, rng).unwrap(); + // Ensure the partially_verified_transactions cache is not updated. + assert!(vm.partially_verified_transactions.read().peek(&deployment.to_deployment_id().unwrap()).is_none()); } #[test] @@ -458,12 +481,23 @@ mod tests { assert!(execution.proof().is_some()); // Verify the execution. vm.check_execution_internal(&execution, false).unwrap(); + // Ensure the partially_verified_transactions cache is not updated. + assert!( + vm.partially_verified_transactions.read().peek(&execution.to_execution_id().unwrap()).is_none() + ); // Ensure that deserialization doesn't break the transaction verification. let serialized_execution = execution.to_string(); let recovered_execution: Execution = serde_json::from_str(&serialized_execution).unwrap(); vm.check_execution_internal(&recovered_execution, false).unwrap(); + // Ensure the partially_verified_transactions cache is not updated. + assert!( + vm.partially_verified_transactions + .read() + .peek(&recovered_execution.to_execution_id().unwrap()) + .is_none() + ); } _ => panic!("Expected an execution transaction"), } @@ -489,12 +523,16 @@ mod tests { // Ensure the proof exists. assert!(fee.proof().is_some()); // Verify the fee. - vm.check_fee_internal(&fee, execution_id).unwrap(); + vm.check_fee_internal(&fee, execution_id, false).unwrap(); + // Ensure the partially_verified_transactions cache is not updated. + assert!(vm.partially_verified_transactions.read().peek(&execution_id).is_none()); // Ensure that deserialization doesn't break the transaction verification. let serialized_fee = fee.to_string(); let recovered_fee: Fee = serde_json::from_str(&serialized_fee).unwrap(); - vm.check_fee_internal(&recovered_fee, execution_id).unwrap(); + vm.check_fee_internal(&recovered_fee, execution_id, false).unwrap(); + // Ensure the partially_verified_transactions cache is not updated. + assert!(vm.partially_verified_transactions.read().peek(&execution_id).is_none()); } _ => panic!("Expected an execution with a fee"), } @@ -515,14 +553,20 @@ mod tests { // Fetch a valid execution transaction with a private fee. let valid_transaction = crate::vm::test_helpers::sample_execution_transaction_with_private_fee(rng); vm.check_transaction(&valid_transaction, None, rng).unwrap(); + // Ensure the partially_verified_transactions cache is updated. + assert!(vm.partially_verified_transactions.read().peek(&valid_transaction.id()).is_some()); // Fetch a valid execution transaction with a public fee. let valid_transaction = crate::vm::test_helpers::sample_execution_transaction_with_public_fee(rng); vm.check_transaction(&valid_transaction, None, rng).unwrap(); + // Ensure the partially_verified_transactions cache is updated. + assert!(vm.partially_verified_transactions.read().peek(&valid_transaction.id()).is_some()); - // Fetch an valid execution transaction with no fee. + // Fetch a valid execution transaction with no fee. let valid_transaction = crate::vm::test_helpers::sample_execution_transaction_without_fee(rng); vm.check_transaction(&valid_transaction, None, rng).unwrap(); + // Ensure the partially_verified_transactions cache is updated. + assert!(vm.partially_verified_transactions.read().peek(&valid_transaction.id()).is_some()); } #[test] @@ -630,6 +674,8 @@ mod tests { // Verify. vm.check_transaction(&transaction, None, rng).unwrap(); + // Ensure the partially_verified_transactions cache is updated. + assert!(vm.partially_verified_transactions.read().peek(&transaction.id()).is_some()); } #[test] @@ -679,6 +725,8 @@ function compute: // Fetch a valid execution transaction with a public fee. let valid_transaction = crate::vm::test_helpers::sample_execution_transaction_with_public_fee(rng); vm.check_transaction(&valid_transaction, None, rng).unwrap(); + // Ensure the partially_verified_transactions cache is updated. + assert!(vm.partially_verified_transactions.read().peek(&valid_transaction.id()).is_some()); // Mutate the execution transaction by inserting a Field::Zero as an output. let execution = valid_transaction.execution().unwrap(); @@ -730,6 +778,8 @@ function compute: // Ensure that the mutated transaction fails verification due to an extra output. assert!(vm.check_transaction(&mutated_transaction, None, rng).is_err()); + // Ensure the partially_verified_transactions cache is not updated. + assert!(vm.partially_verified_transactions.read().peek(&mutated_transaction.id()).is_none()); } #[cfg(feature = "test")] @@ -760,6 +810,8 @@ function compute: minimum_credits_transfer_public_fee, ); assert!(vm.check_transaction(&fee_too_low_transaction, None, rng).is_err()); + // Ensure the partially_verified_transactions cache is not updated. + assert!(vm.partially_verified_transactions.read().peek(&fee_too_low_transaction.id()).is_none()); // Try to submit a tx with the old fee before the migration block height let old_valid_transaction = crate::vm::test_helpers::create_new_transaction_with_different_fee( @@ -768,6 +820,8 @@ function compute: old_minimum_credits_transfer_public_fee, ); assert!(vm.check_transaction(&old_valid_transaction, None, rng).is_ok()); + // Ensure the partially_verified_transactions cache is updated. + assert!(vm.partially_verified_transactions.read().peek(&old_valid_transaction.id()).is_some()); // Update the VM to the migration block height let private_key = test_helpers::sample_genesis_private_key(rng); @@ -811,6 +865,8 @@ function compute: old_minimum_credits_transfer_public_fee, ); assert!(vm.check_transaction(&fee_too_high_transaction, None, rng).is_ok()); + // Ensure the partially_verified_transactions cache is updated. + assert!(vm.partially_verified_transactions.read().peek(&fee_too_high_transaction.id()).is_some()); // Try to submit a tx with the new fee after the migration block height let valid_transaction = crate::vm::test_helpers::create_new_transaction_with_different_fee( @@ -819,5 +875,7 @@ function compute: minimum_credits_transfer_public_fee, ); assert!(vm.check_transaction(&valid_transaction, None, rng).is_ok()); + // Ensure the partially_verified_transactions cache is updated. + assert!(vm.partially_verified_transactions.read().peek(&valid_transaction.id()).is_some()); } }