Skip to content

Commit

Permalink
Merge pull request #2270 from ljedrz/perf/cache_tx_checks
Browse files Browse the repository at this point in the history
Cache transaction checks
  • Loading branch information
howardwu authored Feb 13, 2024
2 parents a87cb65 + f988057 commit 9d4bdea
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 13 deletions.
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions synthesizer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ version = "1.0"
version = "2.0"
features = [ "serde", "rayon" ]

[dependencies.lru]
version = "0.12"

[dependencies.parking_lot]
version = "0.12"

Expand Down
1 change: 1 addition & 0 deletions synthesizer/process/src/verify_deployment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ impl<N: Network> Process<N> {
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.
Expand Down
14 changes: 13 additions & 1 deletion synthesizer/src/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand All @@ -76,6 +77,8 @@ pub struct VM<N: Network, C: ConsensusStorage<N>> {
atomic_lock: Arc<Mutex<()>>,
/// The lock for ensuring there is no concurrency when advancing blocks.
block_lock: Arc<Mutex<()>>,
/// A cache containing the list of recent partially-verified transactions.
partially_verified_transactions: Arc<RwLock<LruCache<N::TransactionID, ()>>>,
}

impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
Expand Down Expand Up @@ -178,6 +181,9 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
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::<console::network::Testnet3>::MAX_TRANSACTIONS).unwrap(),
))),
})
}

Expand All @@ -192,6 +198,12 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
pub fn process(&self) -> Arc<RwLock<Process<N>>> {
self.process.clone()
}

/// Returns the partially-verified transactions.
#[inline]
pub fn partially_verified_transactions(&self) -> Arc<RwLock<LruCache<N::TransactionID, ()>>> {
self.partially_verified_transactions.clone()
}
}

impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
Expand Down
42 changes: 30 additions & 12 deletions synthesizer/src/vm/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
// 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, _) => {
Expand All @@ -108,12 +111,18 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
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.
Expand All @@ -125,11 +134,17 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
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(())
}
Expand Down Expand Up @@ -229,22 +244,25 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
/// 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<N>) -> Result<()> {
fn check_execution_internal(&self, execution: &Execution<N>, 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.
let result = match verification {
// 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
Expand Down Expand Up @@ -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<CurrentNetwork> =
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"),
}
Expand Down

0 comments on commit 9d4bdea

Please sign in to comment.