diff --git a/Cargo.lock b/Cargo.lock index 40ffced9aa..ab744cc8a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1467,6 +1467,15 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "lru" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2c024b41519440580066ba82aab04092b333e09066a5eb86c7c4890df31f22" +dependencies = [ + "hashbrown 0.14.3", +] + [[package]] name = "lz4-sys" version = "1.9.4" @@ -3346,6 +3355,7 @@ dependencies = [ "criterion", "indexmap 2.1.0", "itertools 0.11.0", + "lru", "once_cell", "parking_lot", "rand", diff --git a/synthesizer/Cargo.toml b/synthesizer/Cargo.toml index 4371953852..40f9fcc0d2 100644 --- a/synthesizer/Cargo.toml +++ b/synthesizer/Cargo.toml @@ -137,6 +137,9 @@ version = "1.0" version = "2.0" features = [ "serde", "rayon" ] +[dependencies.lru] +version = "0.12" + [dependencies.parking_lot] version = "0.12" diff --git a/synthesizer/process/src/verify_deployment.rs b/synthesizer/process/src/verify_deployment.rs index 15d5f6e2ea..f499fdebcc 100644 --- a/synthesizer/process/src/verify_deployment.rs +++ b/synthesizer/process/src/verify_deployment.rs @@ -23,6 +23,7 @@ impl Process { rng: &mut R, ) -> Result<()> { let timer = timer!("Process::verify_deployment"); + // Retrieve the program ID. let program_id = deployment.program().id(); // Ensure the program does not already exist in the process. diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index 09389c6c74..d5762da826 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -60,8 +60,9 @@ use synthesizer_program::{FinalizeGlobalState, FinalizeOperation, FinalizeStoreT use aleo_std::prelude::{finish, lap, timer}; use indexmap::{IndexMap, IndexSet}; +use lru::LruCache; use parking_lot::{Mutex, RwLock}; -use std::sync::Arc; +use std::{num::NonZeroUsize, sync::Arc}; #[cfg(not(feature = "serial"))] use rayon::prelude::*; @@ -76,6 +77,8 @@ pub struct VM> { atomic_lock: Arc>, /// The lock for ensuring there is no concurrency when advancing blocks. block_lock: Arc>, + /// A cache containing the list of recent partially-verified transactions. + partially_verified_transactions: Arc>>, } impl> VM { @@ -178,6 +181,9 @@ impl> VM { store, atomic_lock: Arc::new(Mutex::new(())), block_lock: Arc::new(Mutex::new(())), + partially_verified_transactions: Arc::new(RwLock::new(LruCache::new( + NonZeroUsize::new(Transactions::::MAX_TRANSACTIONS).unwrap(), + ))), }) } @@ -192,6 +198,12 @@ impl> VM { pub fn process(&self) -> Arc>> { self.process.clone() } + + /// Returns the partially-verified transactions. + #[inline] + pub fn partially_verified_transactions(&self) -> Arc>> { + self.partially_verified_transactions.clone() + } } impl> VM { diff --git a/synthesizer/src/vm/verify.rs b/synthesizer/src/vm/verify.rs index fe036b7e3a..3892d153db 100644 --- a/synthesizer/src/vm/verify.rs +++ b/synthesizer/src/vm/verify.rs @@ -95,6 +95,9 @@ impl> VM { // First, verify the fee. self.check_fee(transaction, rejected_id)?; + // Check if the transaction exists in the partially-verified cache. + let is_partially_verified = self.partially_verified_transactions.read().peek(&transaction.id()).is_some(); + // Next, verify the deployment or execution. match transaction { Transaction::Deploy(id, owner, deployment, _) => { @@ -108,12 +111,18 @@ impl> VM { if deployment.edition() != N::EDITION { bail!("Invalid deployment transaction '{id}' - expected edition {}", N::EDITION) } - // Ensure the program ID does not already exist.. + // Ensure the program ID does not already exist in the store. if self.transaction_store().contains_program_id(deployment.program_id())? { bail!("Program ID '{}' is already deployed", deployment.program_id()) } - // Verify the deployment. - self.check_deployment_internal(deployment, rng)?; + // Ensure the program does not already exist in the process. + if self.contains_program(deployment.program_id()) { + bail!("Program ID '{}' already exists", deployment.program_id()); + } + // Verify the deployment if it has not been verified before. + if !is_partially_verified { + self.check_deployment_internal(deployment, rng)?; + } } Transaction::Execute(id, execution, _) => { // Compute the execution ID. @@ -125,11 +134,17 @@ impl> VM { bail!("Transaction '{id}' contains a previously rejected execution") } // Verify the execution. - self.check_execution_internal(execution)?; + self.check_execution_internal(execution, is_partially_verified)?; } Transaction::Fee(..) => { /* no-op */ } } + // If the above checks have passed and this is not a fee transaction, + // then add the transaction ID to the partially-verified transactions cache. + if !matches!(transaction, Transaction::Fee(..)) && !is_partially_verified { + self.partially_verified_transactions.write().push(transaction.id(), ()); + } + finish!(timer, "Verify the transaction"); Ok(()) } @@ -229,11 +244,14 @@ impl> VM { /// Note: This is an internal check only. To ensure all components of the execution are checked, /// use `VM::check_transaction` instead. #[inline] - fn check_execution_internal(&self, execution: &Execution) -> Result<()> { + fn check_execution_internal(&self, execution: &Execution, is_partially_verified: bool) -> Result<()> { let timer = timer!("VM::check_execution"); - // Verify the execution. - let verification = self.process.read().verify_execution(execution); + // Verify the execution proof, if it has not been partially-verified before. + let verification = match is_partially_verified { + true => Ok(()), + false => self.process.read().verify_execution(execution), + }; lap!(timer, "Verify the execution"); // Ensure the global state root exists in the block store. @@ -241,10 +259,10 @@ impl> VM { // Ensure the global state root exists in the block store. Ok(()) => match self.block_store().contains_state_root(&execution.global_state_root()) { Ok(true) => Ok(()), - Ok(false) => bail!("Execution verification failed: global state root not found"), - Err(error) => bail!("Execution verification failed: {error}"), + Ok(false) => bail!("Execution verification failed - global state root does not exist (yet)"), + Err(error) => bail!("Execution verification failed - {error}"), }, - Err(error) => bail!("Execution verification failed: {error}"), + Err(error) => bail!("Execution verification failed - {error}"), }; finish!(timer, "Check the global state root"); result @@ -373,13 +391,13 @@ mod tests { // Ensure the proof exists. assert!(execution.proof().is_some()); // Verify the execution. - vm.check_execution_internal(&execution).unwrap(); + vm.check_execution_internal(&execution, false).unwrap(); // 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).unwrap(); + vm.check_execution_internal(&recovered_execution, false).unwrap(); } _ => panic!("Expected an execution transaction"), }