From 550ac43ac7719724c82e1152a340a8fd33f681a9 Mon Sep 17 00:00:00 2001 From: raychu86 <14917648+raychu86@users.noreply.github.com> Date: Tue, 12 Mar 2024 13:46:37 -0700 Subject: [PATCH 1/9] Add a dedicated check_transactions function --- ledger/src/check_transaction_basic.rs | 9 ++++++++ synthesizer/src/vm/mod.rs | 1 + synthesizer/src/vm/verify.rs | 32 +++++++++++++++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/ledger/src/check_transaction_basic.rs b/ledger/src/check_transaction_basic.rs index cfdb6b7a76..8d0110cac5 100644 --- a/ledger/src/check_transaction_basic.rs +++ b/ledger/src/check_transaction_basic.rs @@ -24,4 +24,13 @@ impl> Ledger { ) -> Result<()> { self.vm().check_transaction(transaction, rejected_id, rng) } + + /// Checks that the given list of transactions are well-formed and unique. + pub fn check_transactions_basic( + &self, + transactions: &[(&Transaction, Option>)], + rng: &mut R, + ) -> Result<()> { + self.vm().check_transactions(transactions, rng) + } } diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index 795499689b..5b323b1118 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -62,6 +62,7 @@ use aleo_std::prelude::{finish, lap, timer}; use indexmap::{IndexMap, IndexSet}; use lru::LruCache; use parking_lot::{Mutex, RwLock}; +use rand::{rngs::StdRng, SeedableRng}; use std::{num::NonZeroUsize, sync::Arc}; #[cfg(not(feature = "serial"))] diff --git a/synthesizer/src/vm/verify.rs b/synthesizer/src/vm/verify.rs index 285565325f..fadb1defc1 100644 --- a/synthesizer/src/vm/verify.rs +++ b/synthesizer/src/vm/verify.rs @@ -31,6 +31,38 @@ macro_rules! ensure_is_unique { }; } +impl> VM { + /// The maximum number of deployments to verify in parallel. + const MAX_PARALLEL_DEPLOY_VERIFICATIONS: usize = 5; + /// The maximum number of executions to verify in parallel. + const MAX_PARALLEL_EXECUTE_VERIFICATIONS: usize = 1000; + + /// Verifies the list of transactions in the VM. On failure, returns an error. + pub fn check_transactions( + &self, + transactions: &[(&Transaction, Option>)], + rng: &mut R, + ) -> Result<()> { + // Separate the transactions into deploys and executions. + let (deployments, executions): (Vec<_>, Vec<_>) = transactions.iter().partition(|(tx, _)| tx.is_deploy()); + // Chunk the deploys and executions into groups for parallel verification. + let deployments_for_verification = deployments.chunks(Self::MAX_PARALLEL_DEPLOY_VERIFICATIONS); + let executions_for_verification = executions.chunks(Self::MAX_PARALLEL_EXECUTE_VERIFICATIONS); + + // Verify the transactions in batches. + for transactions in deployments_for_verification.chain(executions_for_verification) { + // Ensure each transaction is well-formed and unique. + let rngs = (0..transactions.len()).map(|_| StdRng::from_seed(rng.gen())).collect::>(); + cfg_iter!(transactions).zip(rngs).try_for_each(|((transaction, rejected_id), mut rng)| { + self.check_transaction(transaction, *rejected_id, &mut rng) + .map_err(|e| anyhow!("Invalid transaction found in the transactions list: {e}")) + })?; + } + + Ok(()) + } +} + impl> VM { /// Verifies the transaction in the VM. On failure, returns an error. #[inline] From 11ada215a4d793e6391d9361ddd224a12e64a6d7 Mon Sep 17 00:00:00 2001 From: raychu86 <14917648+raychu86@users.noreply.github.com> Date: Tue, 12 Mar 2024 13:49:50 -0700 Subject: [PATCH 2/9] Use bounded parallel transaction verification --- ledger/src/check_next_block.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/ledger/src/check_next_block.rs b/ledger/src/check_next_block.rs index c23797f8d4..1c5a21f840 100644 --- a/ledger/src/check_next_block.rs +++ b/ledger/src/check_next_block.rs @@ -14,8 +14,6 @@ use super::*; -use rand::{rngs::StdRng, SeedableRng}; - impl> Ledger { /// Checks the given block is valid next block. pub fn check_next_block(&self, block: &Block, rng: &mut R) -> Result<()> { @@ -38,13 +36,14 @@ impl> Ledger { } } + // Retrieve the transactions and their rejected IDs. + let transactions = block + .transactions() + .iter() + .map(|transaction| transaction.to_rejected_id().map(|rejected_id| (transaction.deref(), rejected_id))) + .collect::>>()?; // Ensure each transaction is well-formed and unique. - let transactions = block.transactions(); - let rngs = (0..transactions.len()).map(|_| StdRng::from_seed(rng.gen())).collect::>(); - cfg_iter!(transactions).zip(rngs).try_for_each(|(transaction, mut rng)| { - self.check_transaction_basic(transaction, transaction.to_rejected_id()?, &mut rng) - .map_err(|e| anyhow!("Invalid transaction found in the transactions list: {e}")) - })?; + self.check_transactions_basic(&transactions, rng)?; // TODO (howardwu): Remove this after moving the total supply into credits.aleo. { From fa9b8c6a494e98abb2623cc893ba76acc4ed9bb8 Mon Sep 17 00:00:00 2001 From: raychu86 <14917648+raychu86@users.noreply.github.com> Date: Thu, 14 Mar 2024 15:23:30 -0700 Subject: [PATCH 3/9] Use dedicated function for pre-speculate verification --- synthesizer/src/vm/finalize.rs | 34 +++++++---------------- synthesizer/src/vm/verify.rs | 49 ++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 25 deletions(-) diff --git a/synthesizer/src/vm/finalize.rs b/synthesizer/src/vm/finalize.rs index 216b8d13fb..1c5bef40ac 100644 --- a/synthesizer/src/vm/finalize.rs +++ b/synthesizer/src/vm/finalize.rs @@ -47,32 +47,16 @@ impl> VM { let candidate_transactions: Vec<_> = candidate_transactions.collect::>(); let candidate_transaction_ids: Vec<_> = candidate_transactions.iter().map(|tx| tx.id()).collect(); + // Determine if the vm is currently processing the genesis block. + let is_genesis = + self.block_store().find_block_height_from_state_root(self.block_store().current_state_root())?.is_none(); // If the transactions are not part of the genesis block, ensure each transaction is well-formed and unique. Abort any transactions that are not. - let (verified_transactions, verification_aborted_transactions) = - match self.block_store().find_block_height_from_state_root(self.block_store().current_state_root())? { - // If the current state root does not exist in the block store, then the genesis block has not been introduced yet. - None => (candidate_transactions, vec![]), - // Verify transactions for all non-genesis cases. - _ => { - let rngs = - (0..candidate_transactions.len()).map(|_| StdRng::from_seed(rng.gen())).collect::>(); - // Verify the transactions and collect the error message if there is one. - cfg_into_iter!(candidate_transactions).zip(rngs).partition_map(|(transaction, mut rng)| { - // Abort the transaction if it is a fee transaction. - if transaction.is_fee() { - return Either::Right(( - transaction, - "Fee transactions are not allowed in speculate".to_string(), - )); - } - // Verify the transaction. - match self.check_transaction(transaction, None, &mut rng) { - Ok(_) => Either::Left(transaction), - Err(e) => Either::Right((transaction, e.to_string())), - } - }) - } - }; + let (verified_transactions, verification_aborted_transactions) = match is_genesis { + // If the current state root does not exist in the block store, then the genesis block has not been introduced yet. + true => (candidate_transactions, vec![]), + // Verify transactions for all non-genesis cases. + false => self.prepare_transactions_for_speculate(&candidate_transactions, rng)?, + }; // Performs a **dry-run** over the list of ratifications, solutions, and transactions. let (ratifications, confirmed_transactions, speculation_aborted_transactions, ratified_finalize_operations) = diff --git a/synthesizer/src/vm/verify.rs b/synthesizer/src/vm/verify.rs index 0ea035c530..df7cb5c80f 100644 --- a/synthesizer/src/vm/verify.rs +++ b/synthesizer/src/vm/verify.rs @@ -61,6 +61,55 @@ impl> VM { Ok(()) } + + /// Prepares the transactions for speculation. This function returns a list of valid transactions + /// and a list of transactions to be aborted. + /// + /// Transactions will be aborted if they are a fee transaction, or if they are invalid. + pub(crate) fn prepare_transactions_for_speculate<'a, R: CryptoRng + Rng>( + &self, + transactions: &[&'a Transaction], + rng: &mut R, + ) -> Result<(Vec<&'a Transaction>, Vec<(&'a Transaction, String)>)> { + // Construct the list of valid and invalid transactions. + let mut valid_transactions = Vec::with_capacity(transactions.len()); + let mut aborted_transactions = Vec::with_capacity(transactions.len()); + + // Separate the transactions into deploys and executions. + let (deployments, executions): (Vec<&Transaction>, Vec<&Transaction>) = + transactions.iter().partition(|tx| tx.is_deploy()); + // Chunk the deploys and executions into groups for parallel verification. + let deployments_for_verification = deployments.chunks(Self::MAX_PARALLEL_DEPLOY_VERIFICATIONS); + let executions_for_verification = executions.chunks(Self::MAX_PARALLEL_EXECUTE_VERIFICATIONS); + + // Verify the transactions in batches and separate the valid and invalid transactions. + for transactions in deployments_for_verification.chain(executions_for_verification) { + let rngs = (0..transactions.len()).map(|_| StdRng::from_seed(rng.gen())).collect::>(); + // Verify the transactions and collect the error message if there is one. + let (valid, invalid): (Vec<_>, Vec<_>) = + cfg_into_iter!(transactions).zip(rngs).partition_map(|(transaction, mut rng)| { + // Abort the transaction if it is a fee transaction. + if transaction.is_fee() { + return Either::Right(( + *transaction, + "Fee transactions are not allowed in speculate".to_string(), + )); + } + // Verify the transaction. + match self.check_transaction(transaction, None, &mut rng) { + Ok(_) => Either::Left(*transaction), + Err(e) => Either::Right((*transaction, e.to_string())), + } + }); + + // Collect the valid and aborted transactions. + valid_transactions.extend(valid); + aborted_transactions.extend(invalid); + } + + // Return the valid and invalid transactions. + Ok((valid_transactions, aborted_transactions)) + } } impl> VM { From cec8f524ad4d11fce12c62aa6d0dfc8161cfeb49 Mon Sep 17 00:00:00 2001 From: raychu86 <14917648+raychu86@users.noreply.github.com> Date: Thu, 14 Mar 2024 15:32:54 -0700 Subject: [PATCH 4/9] Use check_transactions in VM::check_speculate --- synthesizer/src/vm/finalize.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/synthesizer/src/vm/finalize.rs b/synthesizer/src/vm/finalize.rs index 1c5bef40ac..8102b6dbe2 100644 --- a/synthesizer/src/vm/finalize.rs +++ b/synthesizer/src/vm/finalize.rs @@ -16,8 +16,6 @@ use super::*; use ledger_committee::{MAX_DELEGATORS, MIN_DELEGATOR_STAKE, MIN_VALIDATOR_STAKE}; -use rand::{rngs::StdRng, SeedableRng}; - impl> VM { /// Speculates on the given list of transactions in the VM. /// This function aborts all transactions that are not are well-formed or unique. @@ -111,15 +109,16 @@ impl> VM { ) -> Result>> { let timer = timer!("VM::check_speculate"); + // Retrieve the transactions and their rejected IDs. + let transactions_and_rejected_ids = transactions + .iter() + .map(|transaction| transaction.to_rejected_id().map(|rejected_id| (transaction.deref(), rejected_id))) + .collect::>>()?; // Ensure each transaction is well-formed and unique. // NOTE: We perform the transaction checks here prior to `atomic_speculate` because we must // ensure that the `Fee` transactions are valid. We can't unify the transaction checks in `atomic_speculate` // because we run speculation on the unconfirmed variant of the transactions. - let rngs = (0..transactions.len()).map(|_| StdRng::from_seed(rng.gen())).collect::>(); - cfg_iter!(transactions).zip(rngs).try_for_each(|(transaction, mut rng)| { - self.check_transaction(transaction, transaction.to_rejected_id()?, &mut rng) - .map_err(|e| anyhow!("Invalid transaction found in the transactions list: {e}")) - })?; + self.check_transactions(&transactions_and_rejected_ids, rng)?; // Reconstruct the candidate ratifications to verify the speculation. let candidate_ratifications = ratifications.iter().cloned().collect::>(); From fbe604de0b9b7035ee07ab903a14c6da5202e8e6 Mon Sep 17 00:00:00 2001 From: raychu86 <14917648+raychu86@users.noreply.github.com> Date: Fri, 22 Mar 2024 13:29:36 -0700 Subject: [PATCH 5/9] Catch and abort panicking transactions --- Cargo.lock | 1 + synthesizer/Cargo.toml | 5 +++++ synthesizer/src/vm/mod.rs | 32 +++++++++++++++++++++++++++++--- synthesizer/src/vm/verify.rs | 16 +++++++++++++--- 4 files changed, 48 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f91e9879a7..5ad1983b7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3375,6 +3375,7 @@ dependencies = [ "snarkvm-synthesizer-process", "snarkvm-synthesizer-program", "snarkvm-synthesizer-snark", + "snarkvm-utilities", "tracing", "walkdir", ] diff --git a/synthesizer/Cargo.toml b/synthesizer/Cargo.toml index 42a5bdfdbb..d7e8c2d21c 100644 --- a/synthesizer/Cargo.toml +++ b/synthesizer/Cargo.toml @@ -120,6 +120,11 @@ path = "./snark" version = "=0.16.19" optional = true +[dependencies.utilities] +package = "snarkvm-utilities" +path = "../utilities" +version = "=0.16.19" + [dependencies.aleo-std] version = "0.1.24" default-features = false diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index 69515afc09..004f5234ab 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -57,6 +57,7 @@ use ledger_store::{ }; use synthesizer_process::{deployment_cost, execution_cost, Authorization, Process, Trace}; use synthesizer_program::{FinalizeGlobalState, FinalizeOperation, FinalizeStoreTrait, Program}; +use utilities::handle_halting; use aleo_std::prelude::{finish, lap, timer}; use indexmap::{IndexMap, IndexSet}; @@ -702,7 +703,6 @@ function compute: // Construct the new block header. let (ratifications, transactions, aborted_transaction_ids, ratified_finalize_operations) = vm.speculate(sample_finalize_state(1), None, vec![], &None.into(), transactions.iter(), rng)?; - assert!(aborted_transaction_ids.is_empty()); // Construct the metadata associated with the block. let metadata = Metadata::new( @@ -1269,12 +1269,12 @@ function do: } #[test] - #[should_panic] fn test_deployment_synthesis_underreport() { let rng = &mut TestRng::default(); // Initialize a private key. let private_key = sample_genesis_private_key(rng); + let address = Address::try_from(&private_key).unwrap(); // Initialize the genesis block. let genesis = sample_genesis_block(rng); @@ -1319,7 +1319,33 @@ function do: let adjusted_transaction = Transaction::Deploy(txid, program_owner, Box::new(adjusted_deployment), fee); // Verify the deployment transaction. It should panic when enforcing the first constraint over the vk limit. - let _ = vm.check_transaction(&adjusted_transaction, None, rng); + let result = vm.check_transaction(&adjusted_transaction, None, rng); + assert!(result.is_err()); + // Check that the error message mentions the panic. + if let Err(err) = result { + assert!(err.to_string().contains("panic")); + } + + // Create a standard transaction + // Prepare the inputs. + let inputs = [ + Value::::from_str(&address.to_string()).unwrap(), + Value::::from_str("1u64").unwrap(), + ] + .into_iter(); + + // Execute. + let transaction = + vm.execute(&private_key, ("credits.aleo", "transfer_public"), inputs, None, 0, None, rng).unwrap(); + + // Check that the deployment transaction will be aborted if injected into a block. + let block = sample_next_block(&vm, &private_key, &[transaction, adjusted_transaction.clone()], rng).unwrap(); + + // Check that the block aborts the deployment transaction. + assert_eq!(block.aborted_transaction_ids(), &vec![adjusted_transaction.id()]); + + // Update the VM. + vm.add_next_block(&block).unwrap(); } #[test] diff --git a/synthesizer/src/vm/verify.rs b/synthesizer/src/vm/verify.rs index df7cb5c80f..e5885524ab 100644 --- a/synthesizer/src/vm/verify.rs +++ b/synthesizer/src/vm/verify.rs @@ -202,7 +202,12 @@ impl> VM { } // Verify the deployment if it has not been verified before. if !is_partially_verified { - self.check_deployment_internal(deployment, rng)?; + match handle_halting!(panic::AssertUnwindSafe(|| { + self.check_deployment_internal(deployment, rng) + })) { + Ok(result) => result?, + Err(_) => bail!("Transaction '{id}' panicked during verification"), + } } } Transaction::Execute(id, execution, _) => { @@ -214,8 +219,13 @@ impl> VM { if self.block_store().contains_rejected_deployment_or_execution_id(&execution_id)? { bail!("Transaction '{id}' contains a previously rejected execution") } - // Verify the execution. - self.check_execution_internal(execution, is_partially_verified)?; + // Verify the execution and catch any panics. + match handle_halting!(panic::AssertUnwindSafe(|| { + self.check_execution_internal(execution, is_partially_verified) + })) { + Ok(result) => result?, + Err(_) => bail!("Transaction '{id}' panicked during verification"), + } } Transaction::Fee(..) => { /* no-op */ } } From 704a3239be76eefe3b9ad641f9b726ff7b11bd78 Mon Sep 17 00:00:00 2001 From: raychu86 <14917648+raychu86@users.noreply.github.com> Date: Fri, 22 Mar 2024 13:32:36 -0700 Subject: [PATCH 6/9] Nit --- synthesizer/src/vm/verify.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/synthesizer/src/vm/verify.rs b/synthesizer/src/vm/verify.rs index e5885524ab..dea8f25149 100644 --- a/synthesizer/src/vm/verify.rs +++ b/synthesizer/src/vm/verify.rs @@ -202,6 +202,7 @@ impl> VM { } // Verify the deployment if it has not been verified before. if !is_partially_verified { + // Catch any panics during verification. match handle_halting!(panic::AssertUnwindSafe(|| { self.check_deployment_internal(deployment, rng) })) { From 10b67c9fee0d4395c8b5a08817333db594acfdfc Mon Sep 17 00:00:00 2001 From: raychu86 <14917648+raychu86@users.noreply.github.com> Date: Wed, 27 Mar 2024 13:24:45 -0400 Subject: [PATCH 7/9] Clean up comments --- synthesizer/process/src/finalize.rs | 10 +++++----- synthesizer/src/vm/mod.rs | 8 ++------ synthesizer/src/vm/verify.rs | 14 ++++++-------- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/synthesizer/process/src/finalize.rs b/synthesizer/process/src/finalize.rs index 2f86675e7d..5421f92f5e 100644 --- a/synthesizer/process/src/finalize.rs +++ b/synthesizer/process/src/finalize.rs @@ -17,7 +17,7 @@ use console::program::{FinalizeType, Future, Register}; use synthesizer_program::{Await, FinalizeRegistersState, Operand}; use utilities::handle_halting; -use std::collections::HashSet; +use std::{collections::HashSet, panic::AssertUnwindSafe}; impl Process { /// Finalizes the deployment and fee. @@ -227,7 +227,7 @@ fn finalize_transition>( // Finalize the command. match &command { Command::BranchEq(branch_eq) => { - let result = handle_halting!(panic::AssertUnwindSafe(|| { + let result = handle_halting!(AssertUnwindSafe(|| { branch_to(counter, branch_eq, finalize, stack, ®isters) })); match result { @@ -241,7 +241,7 @@ fn finalize_transition>( } } Command::BranchNeq(branch_neq) => { - let result = handle_halting!(panic::AssertUnwindSafe(|| { + let result = handle_halting!(AssertUnwindSafe(|| { branch_to(counter, branch_neq, finalize, stack, ®isters) })); match result { @@ -277,7 +277,7 @@ fn finalize_transition>( None => bail!("Transition ID '{transition_id}' not found in call graph"), }; - let callee_state = match handle_halting!(panic::AssertUnwindSafe(|| { + let callee_state = match handle_halting!(AssertUnwindSafe(|| { // Set up the finalize state for the await. setup_await(state, await_, stack, ®isters, child_transition_id) })) { @@ -307,7 +307,7 @@ fn finalize_transition>( } _ => { let result = - handle_halting!(panic::AssertUnwindSafe(|| { command.finalize(stack, store, &mut registers) })); + handle_halting!(AssertUnwindSafe(|| { command.finalize(stack, store, &mut registers) })); match result { // If the evaluation succeeds with an operation, add it to the list. Ok(Ok(Some(finalize_operation))) => finalize_operations.push(finalize_operation), diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index 061d872113..3b13e4ec21 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -67,7 +67,7 @@ use itertools::Either; use lru::LruCache; use parking_lot::{Mutex, RwLock}; use rand::{rngs::StdRng, SeedableRng}; -use std::{collections::HashSet, num::NonZeroUsize, sync::Arc}; +use std::{collections::HashSet, num::NonZeroUsize, panic::AssertUnwindSafe, sync::Arc}; #[cfg(not(feature = "serial"))] use rayon::prelude::*; @@ -1433,13 +1433,9 @@ function do: Deployment::new(deployment.edition(), deployment.program().clone(), vks_with_underreport).unwrap(); let adjusted_transaction = Transaction::Deploy(txid, program_owner, Box::new(adjusted_deployment), fee); - // Verify the deployment transaction. It should panic when enforcing the first constraint over the vk limit. + // 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); assert!(result.is_err()); - // Check that the error message mentions the panic. - if let Err(err) = result { - assert!(err.to_string().contains("panic")); - } // Create a standard transaction // Prepare the inputs. diff --git a/synthesizer/src/vm/verify.rs b/synthesizer/src/vm/verify.rs index dea8f25149..dc82b0889a 100644 --- a/synthesizer/src/vm/verify.rs +++ b/synthesizer/src/vm/verify.rs @@ -202,12 +202,10 @@ impl> VM { } // Verify the deployment if it has not been verified before. if !is_partially_verified { - // Catch any panics during verification. - match handle_halting!(panic::AssertUnwindSafe(|| { - self.check_deployment_internal(deployment, rng) - })) { + // Verify the deployment. + match handle_halting!(AssertUnwindSafe(|| { self.check_deployment_internal(deployment, rng) })) { Ok(result) => result?, - Err(_) => bail!("Transaction '{id}' panicked during verification"), + Err(_) => bail!("VM safely halted transaction '{id}' during verification"), } } } @@ -220,12 +218,12 @@ impl> VM { if self.block_store().contains_rejected_deployment_or_execution_id(&execution_id)? { bail!("Transaction '{id}' contains a previously rejected execution") } - // Verify the execution and catch any panics. - match handle_halting!(panic::AssertUnwindSafe(|| { + // Verify the execution. + match handle_halting!(AssertUnwindSafe(|| { self.check_execution_internal(execution, is_partially_verified) })) { Ok(result) => result?, - Err(_) => bail!("Transaction '{id}' panicked during verification"), + Err(_) => bail!("VM safely halted transaction '{id}' during verification"), } } Transaction::Fee(..) => { /* no-op */ } From 25e03ffd28ea2705150f84a04094122897759a15 Mon Sep 17 00:00:00 2001 From: raychu86 <14917648+raychu86@users.noreply.github.com> Date: Wed, 27 Mar 2024 13:34:54 -0400 Subject: [PATCH 8/9] Move prepare_for_speculate logic --- synthesizer/src/vm/finalize.rs | 55 ++++++++++++++++++++++++++++++++-- synthesizer/src/vm/verify.rs | 53 ++------------------------------ 2 files changed, 54 insertions(+), 54 deletions(-) diff --git a/synthesizer/src/vm/finalize.rs b/synthesizer/src/vm/finalize.rs index 2511dd4964..1c684ee58a 100644 --- a/synthesizer/src/vm/finalize.rs +++ b/synthesizer/src/vm/finalize.rs @@ -53,7 +53,7 @@ impl> VM { // If the current state root does not exist in the block store, then the genesis block has not been introduced yet. true => (candidate_transactions, vec![]), // Verify transactions for all non-genesis cases. - false => self.prepare_transactions_for_speculate(&candidate_transactions, rng)?, + false => self.prepare_for_speculate(&candidate_transactions, rng)?, }; // Performs a **dry-run** over the list of ratifications, solutions, and transactions. @@ -110,8 +110,7 @@ impl> VM { let timer = timer!("VM::check_speculate"); // Retrieve the transactions and their rejected IDs. - let transactions_and_rejected_ids = transactions - .iter() + let transactions_and_rejected_ids = cfg_iter!(transactions) .map(|transaction| transaction.to_rejected_id().map(|rejected_id| (transaction.deref(), rejected_id))) .collect::>>()?; // Ensure each transaction is well-formed and unique. @@ -789,6 +788,56 @@ impl> VM { }) } + /// Performs precondition checks on the transactions prior to speculation. + /// + /// This method is used to check the following conditions: + /// - If a transaction is a fee transaction or if it is invalid, + /// then the transaction will be aborted. + pub(crate) fn prepare_for_speculate<'a, R: CryptoRng + Rng>( + &self, + transactions: &[&'a Transaction], + rng: &mut R, + ) -> Result<(Vec<&'a Transaction>, Vec<(&'a Transaction, String)>)> { + // Construct the list of valid and invalid transactions. + let mut valid_transactions = Vec::with_capacity(transactions.len()); + let mut aborted_transactions = Vec::with_capacity(transactions.len()); + + // Separate the transactions into deploys and executions. + let (deployments, executions): (Vec<&Transaction>, Vec<&Transaction>) = + transactions.iter().partition(|tx| tx.is_deploy()); + // Chunk the deploys and executions into groups for parallel verification. + let deployments_for_verification = deployments.chunks(Self::MAX_PARALLEL_DEPLOY_VERIFICATIONS); + let executions_for_verification = executions.chunks(Self::MAX_PARALLEL_EXECUTE_VERIFICATIONS); + + // Verify the transactions in batches and separate the valid and invalid transactions. + for transactions in deployments_for_verification.chain(executions_for_verification) { + let rngs = (0..transactions.len()).map(|_| StdRng::from_seed(rng.gen())).collect::>(); + // Verify the transactions and collect the error message if there is one. + let (valid, invalid): (Vec<_>, Vec<_>) = + cfg_into_iter!(transactions).zip(rngs).partition_map(|(transaction, mut rng)| { + // Abort the transaction if it is a fee transaction. + if transaction.is_fee() { + return Either::Right(( + *transaction, + "Fee transactions are not allowed in speculate".to_string(), + )); + } + // Verify the transaction. + match self.check_transaction(transaction, None, &mut rng) { + Ok(_) => Either::Left(*transaction), + Err(e) => Either::Right((*transaction, e.to_string())), + } + }); + + // Collect the valid and aborted transactions. + valid_transactions.extend(valid); + aborted_transactions.extend(invalid); + } + + // Return the valid and invalid transactions. + Ok((valid_transactions, aborted_transactions)) + } + /// Performs precondition checks on the transaction prior to execution. /// /// This method is used to check the following conditions: diff --git a/synthesizer/src/vm/verify.rs b/synthesizer/src/vm/verify.rs index dc82b0889a..4b6c973954 100644 --- a/synthesizer/src/vm/verify.rs +++ b/synthesizer/src/vm/verify.rs @@ -33,9 +33,9 @@ macro_rules! ensure_is_unique { impl> VM { /// The maximum number of deployments to verify in parallel. - const MAX_PARALLEL_DEPLOY_VERIFICATIONS: usize = 5; + pub(crate) const MAX_PARALLEL_DEPLOY_VERIFICATIONS: usize = 5; /// The maximum number of executions to verify in parallel. - const MAX_PARALLEL_EXECUTE_VERIFICATIONS: usize = 1000; + pub(crate) const MAX_PARALLEL_EXECUTE_VERIFICATIONS: usize = 1000; /// Verifies the list of transactions in the VM. On failure, returns an error. pub fn check_transactions( @@ -61,55 +61,6 @@ impl> VM { Ok(()) } - - /// Prepares the transactions for speculation. This function returns a list of valid transactions - /// and a list of transactions to be aborted. - /// - /// Transactions will be aborted if they are a fee transaction, or if they are invalid. - pub(crate) fn prepare_transactions_for_speculate<'a, R: CryptoRng + Rng>( - &self, - transactions: &[&'a Transaction], - rng: &mut R, - ) -> Result<(Vec<&'a Transaction>, Vec<(&'a Transaction, String)>)> { - // Construct the list of valid and invalid transactions. - let mut valid_transactions = Vec::with_capacity(transactions.len()); - let mut aborted_transactions = Vec::with_capacity(transactions.len()); - - // Separate the transactions into deploys and executions. - let (deployments, executions): (Vec<&Transaction>, Vec<&Transaction>) = - transactions.iter().partition(|tx| tx.is_deploy()); - // Chunk the deploys and executions into groups for parallel verification. - let deployments_for_verification = deployments.chunks(Self::MAX_PARALLEL_DEPLOY_VERIFICATIONS); - let executions_for_verification = executions.chunks(Self::MAX_PARALLEL_EXECUTE_VERIFICATIONS); - - // Verify the transactions in batches and separate the valid and invalid transactions. - for transactions in deployments_for_verification.chain(executions_for_verification) { - let rngs = (0..transactions.len()).map(|_| StdRng::from_seed(rng.gen())).collect::>(); - // Verify the transactions and collect the error message if there is one. - let (valid, invalid): (Vec<_>, Vec<_>) = - cfg_into_iter!(transactions).zip(rngs).partition_map(|(transaction, mut rng)| { - // Abort the transaction if it is a fee transaction. - if transaction.is_fee() { - return Either::Right(( - *transaction, - "Fee transactions are not allowed in speculate".to_string(), - )); - } - // Verify the transaction. - match self.check_transaction(transaction, None, &mut rng) { - Ok(_) => Either::Left(*transaction), - Err(e) => Either::Right((*transaction, e.to_string())), - } - }); - - // Collect the valid and aborted transactions. - valid_transactions.extend(valid); - aborted_transactions.extend(invalid); - } - - // Return the valid and invalid transactions. - Ok((valid_transactions, aborted_transactions)) - } } impl> VM { From 0f47547d1544a5bc30e2a2ea09f46c0e8774f1d5 Mon Sep 17 00:00:00 2001 From: raychu86 <14917648+raychu86@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:00:12 -0400 Subject: [PATCH 9/9] macro cleanup --- synthesizer/process/src/finalize.rs | 34 ++++++++++++----------------- synthesizer/src/vm/mod.rs | 4 ++-- synthesizer/src/vm/verify.rs | 6 ++--- utilities/src/error.rs | 12 +++++----- 4 files changed, 24 insertions(+), 32 deletions(-) diff --git a/synthesizer/process/src/finalize.rs b/synthesizer/process/src/finalize.rs index 5421f92f5e..5b420ac064 100644 --- a/synthesizer/process/src/finalize.rs +++ b/synthesizer/process/src/finalize.rs @@ -15,9 +15,9 @@ use super::*; use console::program::{FinalizeType, Future, Register}; use synthesizer_program::{Await, FinalizeRegistersState, Operand}; -use utilities::handle_halting; +use utilities::try_vm_runtime; -use std::{collections::HashSet, panic::AssertUnwindSafe}; +use std::collections::HashSet; impl Process { /// Finalizes the deployment and fee. @@ -227,9 +227,7 @@ fn finalize_transition>( // Finalize the command. match &command { Command::BranchEq(branch_eq) => { - let result = handle_halting!(AssertUnwindSafe(|| { - branch_to(counter, branch_eq, finalize, stack, ®isters) - })); + let result = try_vm_runtime!(|| branch_to(counter, branch_eq, finalize, stack, ®isters)); match result { Ok(Ok(new_counter)) => { counter = new_counter; @@ -241,9 +239,7 @@ fn finalize_transition>( } } Command::BranchNeq(branch_neq) => { - let result = handle_halting!(AssertUnwindSafe(|| { - branch_to(counter, branch_neq, finalize, stack, ®isters) - })); + let result = try_vm_runtime!(|| branch_to(counter, branch_neq, finalize, stack, ®isters)); match result { Ok(Ok(new_counter)) => { counter = new_counter; @@ -277,16 +273,15 @@ fn finalize_transition>( None => bail!("Transition ID '{transition_id}' not found in call graph"), }; - let callee_state = match handle_halting!(AssertUnwindSafe(|| { - // Set up the finalize state for the await. - setup_await(state, await_, stack, ®isters, child_transition_id) - })) { - Ok(Ok(callee_state)) => callee_state, - // If the evaluation fails, bail and return the error. - Ok(Err(error)) => bail!("'finalize' failed to evaluate command ({command}): {error}"), - // If the evaluation fails, bail and return the error. - Err(_) => bail!("'finalize' failed to evaluate command ({command})"), - }; + // Set up the finalize state for the await. + let callee_state = + match try_vm_runtime!(|| setup_await(state, await_, stack, ®isters, child_transition_id)) { + Ok(Ok(callee_state)) => callee_state, + // If the evaluation fails, bail and return the error. + Ok(Err(error)) => bail!("'finalize' failed to evaluate command ({command}): {error}"), + // If the evaluation fails, bail and return the error. + Err(_) => bail!("'finalize' failed to evaluate command ({command})"), + }; // Increment the call counter. call_counter += 1; @@ -306,8 +301,7 @@ fn finalize_transition>( continue 'outer; } _ => { - let result = - handle_halting!(AssertUnwindSafe(|| { command.finalize(stack, store, &mut registers) })); + let result = try_vm_runtime!(|| command.finalize(stack, store, &mut registers)); match result { // If the evaluation succeeds with an operation, add it to the list. Ok(Ok(Some(finalize_operation))) => finalize_operations.push(finalize_operation), diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index 3b13e4ec21..5c78a84fb6 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -59,7 +59,7 @@ use ledger_store::{ }; use synthesizer_process::{deployment_cost, execution_cost, Authorization, Process, Trace}; use synthesizer_program::{FinalizeGlobalState, FinalizeOperation, FinalizeStoreTrait, Program}; -use utilities::handle_halting; +use utilities::try_vm_runtime; use aleo_std::prelude::{finish, lap, timer}; use indexmap::{IndexMap, IndexSet}; @@ -67,7 +67,7 @@ use itertools::Either; use lru::LruCache; use parking_lot::{Mutex, RwLock}; use rand::{rngs::StdRng, SeedableRng}; -use std::{collections::HashSet, num::NonZeroUsize, panic::AssertUnwindSafe, sync::Arc}; +use std::{collections::HashSet, num::NonZeroUsize, sync::Arc}; #[cfg(not(feature = "serial"))] use rayon::prelude::*; diff --git a/synthesizer/src/vm/verify.rs b/synthesizer/src/vm/verify.rs index 4b6c973954..5979498bd3 100644 --- a/synthesizer/src/vm/verify.rs +++ b/synthesizer/src/vm/verify.rs @@ -154,7 +154,7 @@ impl> VM { // Verify the deployment if it has not been verified before. if !is_partially_verified { // Verify the deployment. - match handle_halting!(AssertUnwindSafe(|| { self.check_deployment_internal(deployment, rng) })) { + match try_vm_runtime!(|| self.check_deployment_internal(deployment, rng)) { Ok(result) => result?, Err(_) => bail!("VM safely halted transaction '{id}' during verification"), } @@ -170,9 +170,7 @@ impl> VM { bail!("Transaction '{id}' contains a previously rejected execution") } // Verify the execution. - match handle_halting!(AssertUnwindSafe(|| { - self.check_execution_internal(execution, is_partially_verified) - })) { + match try_vm_runtime!(|| self.check_execution_internal(execution, is_partially_verified)) { Ok(result) => result?, Err(_) => bail!("VM safely halted transaction '{id}' during verification"), } diff --git a/utilities/src/error.rs b/utilities/src/error.rs index 97ab38bc65..d63883971a 100644 --- a/utilities/src/error.rs +++ b/utilities/src/error.rs @@ -38,11 +38,11 @@ impl Error for crate::String {} #[cfg(not(feature = "std"))] impl Error for crate::io::Error {} -/// This purpose of this macro is to catch the instances of halting -/// without producing logs looking like unexpected panics. It prints -/// to stderr using the format: "Halted at : ". +/// This macro provides a VM runtime environment which will safely halt +/// without producing logs that look like unexpected behavior. +/// It prints to stderr using the format: "VM safely halted at : ". #[macro_export] -macro_rules! handle_halting { +macro_rules! try_vm_runtime { ($e:expr) => {{ use std::panic; @@ -52,12 +52,12 @@ macro_rules! handle_halting { let msg = e.to_string(); let msg = msg.split_ascii_whitespace().skip_while(|&word| word != "panicked").collect::>(); let mut msg = msg.join(" "); - msg = msg.replacen("panicked", "Halted", 1); + msg = msg.replacen("panicked", "VM safely halted", 1); eprintln!("{msg}"); })); // Perform the operation that may panic. - let result = panic::catch_unwind($e); + let result = panic::catch_unwind(panic::AssertUnwindSafe($e)); // Restore the standard panic hook. let _ = panic::take_hook();