From 0b9933839fd357d03bb133066d6fc655a41418a8 Mon Sep 17 00:00:00 2001 From: Howard Wu <9260812+howardwu@users.noreply.github.com> Date: Fri, 20 Oct 2023 12:47:17 -0700 Subject: [PATCH 1/7] Fixes fee computation for block reward, adds missing fee check --- ledger/block/src/helpers/target.rs | 23 ++- ledger/block/src/transaction/fee/mod.rs | 18 +- .../block/src/transactions/confirmed/mod.rs | 9 + ledger/block/src/verify.rs | 21 +- ledger/src/advance.rs | 45 ++--- ledger/src/check_next_block.rs | 31 +-- ledger/src/check_transaction_basic.rs | 37 +--- ledger/test-helpers/src/lib.rs | 28 +-- synthesizer/process/src/authorize.rs | 10 +- synthesizer/process/src/execute.rs | 15 +- .../process/src/stack/authorization/mod.rs | 2 +- synthesizer/process/src/tests/test_execute.rs | 8 +- synthesizer/src/vm/finalize.rs | 182 +++++++++++++++--- synthesizer/src/vm/mod.rs | 4 +- synthesizer/src/vm/verify.rs | 148 ++++++++------ .../tests/test_vm_execute_and_finalize.rs | 6 +- 16 files changed, 355 insertions(+), 232 deletions(-) diff --git a/ledger/block/src/helpers/target.rs b/ledger/block/src/helpers/target.rs index 60b22bdd08..b396234257 100644 --- a/ledger/block/src/helpers/target.rs +++ b/ledger/block/src/helpers/target.rs @@ -17,20 +17,27 @@ use console::prelude::{ensure, Result}; /// A safety bound (sanity-check) for the coinbase reward. pub const MAX_COINBASE_REWARD: u64 = 190_258_739; // Coinbase reward at block 1. -/// Calculate the block reward, given the total supply, block time, and coinbase reward. -/// R_staking = floor((0.05 * S) / H_Y1) + CR / 2 +/// Calculate the block reward, given the total supply, block time, coinbase reward, and transaction fees. +/// R_staking = floor((0.05 * S) / H_Y1) + CR / 2 + TX_F. /// S = Total supply. /// H_Y1 = Expected block height at year 1. /// CR = Coinbase reward. -pub const fn block_reward(total_supply: u64, block_time: u16, coinbase_reward: u64) -> u64 { +/// TX_F = Transaction fees. +pub const fn block_reward(total_supply: u64, block_time: u16, coinbase_reward: u64, transaction_fees: u64) -> u64 { // Compute the expected block height at year 1. let block_height_at_year_1 = block_height_at_year(block_time, 1); // Compute the annual reward: (0.05 * S). let annual_reward = (total_supply / 1000) * 50; // Compute the block reward: (0.05 * S) / H_Y1. let block_reward = annual_reward / block_height_at_year_1 as u64; - // Return the sum of the block and coinbase rewards. - block_reward + coinbase_reward / 2 + // Return the sum of the block reward, coinbase reward, and transaction fees. + block_reward + (coinbase_reward / 2) + transaction_fees +} + +/// Calculate the puzzle reward, given the coinbase reward. +pub const fn puzzle_reward(coinbase_reward: u64) -> u64 { + // Return the coinbase reward divided by 2. + coinbase_reward / 2 } /// Calculates the coinbase reward for a given block. @@ -270,15 +277,15 @@ mod tests { #[test] fn test_block_reward() { - let reward = block_reward(CurrentNetwork::STARTING_SUPPLY, CurrentNetwork::BLOCK_TIME, 0); + let reward = block_reward(CurrentNetwork::STARTING_SUPPLY, CurrentNetwork::BLOCK_TIME, 0, 0); assert_eq!(reward, EXPECTED_STAKING_REWARD); // Increasing the anchor time will increase the reward. - let larger_reward = block_reward(CurrentNetwork::STARTING_SUPPLY, CurrentNetwork::BLOCK_TIME + 1, 0); + let larger_reward = block_reward(CurrentNetwork::STARTING_SUPPLY, CurrentNetwork::BLOCK_TIME + 1, 0, 0); assert!(reward < larger_reward); // Decreasing the anchor time will decrease the reward. - let smaller_reward = block_reward(CurrentNetwork::STARTING_SUPPLY, CurrentNetwork::BLOCK_TIME - 1, 0); + let smaller_reward = block_reward(CurrentNetwork::STARTING_SUPPLY, CurrentNetwork::BLOCK_TIME - 1, 0, 0); assert!(reward > smaller_reward); } diff --git a/ledger/block/src/transaction/fee/mod.rs b/ledger/block/src/transaction/fee/mod.rs index dfe52803e1..e6e114bbea 100644 --- a/ledger/block/src/transaction/fee/mod.rs +++ b/ledger/block/src/transaction/fee/mod.rs @@ -90,10 +90,10 @@ impl Fee { pub fn amount(&self) -> Result> { // Retrieve the base fee amount. let base_fee_amount = self.base_amount()?; - // Retrieve the priority fee amount. let priority_fee_amount = self.priority_amount()?; - + // Return the amount. + // Note: Use of saturating add is safe, as the sum cannot exceed a u64. Ok(U64::new(base_fee_amount.saturating_add(*priority_fee_amount))) } @@ -109,7 +109,6 @@ impl Fee { }, None => bail!("Missing output in fee transition"), }; - // Retrieve the base fee (in microcredits) as a plaintext value. match self.transition.inputs().get(base_fee_index) { Some(Input::Public(_, Some(Plaintext::Literal(Literal::U64(microcredits), _)))) => Ok(*microcredits), @@ -129,7 +128,6 @@ impl Fee { }, None => bail!("Missing output in fee transition"), }; - // Retrieve the priority fee (in microcredits) as a plaintext value. match self.transition.inputs().get(priority_fee_index) { Some(Input::Public(_, Some(Plaintext::Literal(Literal::U64(microcredits), _)))) => Ok(*microcredits), @@ -236,10 +234,10 @@ pub mod test_helpers { let credits = transaction.records().next().unwrap().1.clone(); // Decrypt the record. let credits = credits.decrypt(&private_key.try_into().unwrap()).unwrap(); - // Set the base fee amount. - let base_fee = 10_000_000; - // Set the priority fee amount. - let priority_fee = 1_000; + // Sample a base fee in microcredits. + let base_fee_in_microcredits = rng.gen_range(1_000_000..u64::MAX / 2); + // Sample a priority fee in microcredits. + let priority_fee_in_microcredits = rng.gen_range(0..u64::MAX / 2); // Initialize the process. let process = Process::load().unwrap(); @@ -248,8 +246,8 @@ pub mod test_helpers { .authorize_fee_private::( &private_key, credits, - base_fee, - priority_fee, + base_fee_in_microcredits, + priority_fee_in_microcredits, deployment_or_execution_id, rng, ) diff --git a/ledger/block/src/transactions/confirmed/mod.rs b/ledger/block/src/transactions/confirmed/mod.rs index e41cd6d474..5ec24dc2e7 100644 --- a/ledger/block/src/transactions/confirmed/mod.rs +++ b/ledger/block/src/transactions/confirmed/mod.rs @@ -265,6 +265,15 @@ impl ConfirmedTransaction { } } + /// Returns the rejected object, if the confirmed transaction is rejected. + pub fn to_rejected(&self) -> Option<&Rejected> { + match self { + ConfirmedTransaction::AcceptedDeploy(..) | ConfirmedTransaction::AcceptedExecute(..) => None, + ConfirmedTransaction::RejectedDeploy(_, _, rejected, _) => Some(rejected), + ConfirmedTransaction::RejectedExecute(_, _, rejected, _) => Some(rejected), + } + } + /// Returns the unconfirmed transaction ID, which is defined as the transaction ID prior to confirmation. /// When a transaction is rejected, its fee transition is used to construct the confirmed transaction ID, /// changing the original transaction ID. diff --git a/ledger/block/src/verify.rs b/ledger/block/src/verify.rs index 63d23e5bb1..4f7b144396 100644 --- a/ledger/block/src/verify.rs +++ b/ledger/block/src/verify.rs @@ -51,19 +51,10 @@ impl Block { expected_proof_target, expected_last_coinbase_target, expected_last_coinbase_timestamp, - mut expected_block_reward, + expected_block_reward, expected_puzzle_reward, ) = self.verify_solutions(previous_block, current_puzzle, current_epoch_challenge)?; - // Add the priority fees to the block reward. - for confirmed_transacation in self.transactions.iter() { - // Get the priority fee amount for the transaction. - let priority_fee_amount = confirmed_transacation.transaction().priority_fee_amount()?; - - // Add the priority fee to the block reward. - expected_block_reward = expected_block_reward.saturating_add(*priority_fee_amount); - } - // Ensure the block ratifications are correct. self.verify_ratifications(expected_block_reward, expected_puzzle_reward)?; @@ -366,10 +357,16 @@ impl Block { u64::try_from(previous_block.cumulative_proof_target())?, previous_block.coinbase_target(), )?; + + // Calculate the expected transaction fees. + let expected_transaction_fees = + self.transactions.iter().map(|tx| Ok(*tx.priority_fee_amount()?)).sum::>()?; + // Compute the expected block reward. - let expected_block_reward = block_reward(N::STARTING_SUPPLY, N::BLOCK_TIME, expected_coinbase_reward); + let expected_block_reward = + block_reward(N::STARTING_SUPPLY, N::BLOCK_TIME, expected_coinbase_reward, expected_transaction_fees); // Compute the expected puzzle reward. - let expected_puzzle_reward = expected_coinbase_reward.saturating_div(2); + let expected_puzzle_reward = puzzle_reward(expected_coinbase_reward); Ok(( expected_cumulative_weight, diff --git a/ledger/src/advance.rs b/ledger/src/advance.rs index f7b7c205af..e7ec366b46 100644 --- a/ledger/src/advance.rs +++ b/ledger/src/advance.rs @@ -187,7 +187,7 @@ impl> Ledger { }; // Calculate the coinbase reward. - let coinbase_reward = ledger_block::coinbase_reward( + let coinbase_reward = coinbase_reward( next_height, N::STARTING_SUPPLY, N::ANCHOR_HEIGHT, @@ -197,34 +197,6 @@ impl> Ledger { latest_coinbase_target, )?; - // Compute the block reward. - let mut block_reward = ledger_block::block_reward(N::STARTING_SUPPLY, N::BLOCK_TIME, coinbase_reward); - // Compute the puzzle reward. - let puzzle_reward = coinbase_reward.saturating_div(2); - - // Add the priority fees to the block reward. - for transaction in candidate_transactions.iter() { - // Get the priority fee for the transaction. - let priority_fee_amount = transaction.priority_fee_amount()?; - // Add the priority fee to the block reward. - block_reward = block_reward.saturating_add(*priority_fee_amount); - } - - // TODO (howardwu): We must first process the candidate ratifications to filter out invalid ratifications. - // We must ensure Ratify::Genesis is only present in the genesis block. - // Construct the ratifications. - // Attention: Do not change the order of the ratifications. - let candidate_ratifications = [Ratify::BlockReward(block_reward), Ratify::PuzzleReward(puzzle_reward)] - .into_iter() - // Lastly, we must append the candidate ratifications. - .chain(candidate_ratifications.into_iter()).collect::>(); - - // Construct the subdag root. - let subdag_root = match subdag { - Some(subdag) => subdag.to_subdag_root()?, - None => Field::zero(), - }; - // Construct the finalize state. let state = FinalizeGlobalState::new::( next_round, @@ -234,12 +206,23 @@ impl> Ledger { previous_block.hash(), )?; // Speculate over the ratifications, solutions, and transactions. - let (ratifications, transactions, aborted_transaction_ids, ratified_finalize_operations) = - self.vm.speculate(state, &candidate_ratifications, solutions.as_ref(), candidate_transactions.iter())?; + let (ratifications, transactions, aborted_transaction_ids, ratified_finalize_operations) = self.vm.speculate( + state, + Some(coinbase_reward), + candidate_ratifications, + solutions.as_ref(), + candidate_transactions.iter(), + )?; // Compute the ratifications root. let ratifications_root = ratifications.to_ratifications_root()?; + // Construct the subdag root. + let subdag_root = match subdag { + Some(subdag) => subdag.to_subdag_root()?, + None => Field::zero(), + }; + // Construct the metadata. let metadata = Metadata::new( N::ID, diff --git a/ledger/src/check_next_block.rs b/ledger/src/check_next_block.rs index b8f36d68d6..578e9e88e1 100644 --- a/ledger/src/check_next_block.rs +++ b/ledger/src/check_next_block.rs @@ -42,7 +42,7 @@ impl> Ledger { // TODO: this intermediate allocation shouldn't be necessary; this is most likely https://github.com/rust-lang/rust/issues/89418. let transactions = block.transactions().iter().collect::>(); cfg_iter!(transactions).try_for_each(|transaction| { - self.check_transaction_basic(*transaction, transaction.to_rejected_id()?) + self.check_transaction_basic(*transaction, transaction.to_rejected()) .map_err(|e| anyhow!("Invalid transaction found in the transactions list: {e}")) })?; @@ -78,32 +78,9 @@ impl> Ledger { block.previous_hash(), )?; - // Reconstruct the candidate ratifications to verify the speculation. - let candidate_ratifications = block.ratifications().iter().cloned().collect::>(); - - // Reconstruct the unconfirmed transactions to verify the speculation. - let unconfirmed_transactions = block - .transactions() - .iter() - .map(|confirmed| confirmed.to_unconfirmed_transaction()) - .collect::>>()?; - - // Speculate over the unconfirmed transactions. - let (ratifications, confirmed_transactions, aborted_transaction_ids, ratified_finalize_operations) = - self.vm.speculate(state, &candidate_ratifications, block.solutions(), unconfirmed_transactions.iter())?; - // Ensure there are no aborted transaction IDs from this speculation. - // Note: There should be no aborted transactions, because we are checking a block, - // where any aborted transactions should be in the aborted transaction ID list, not in transactions. - ensure!(aborted_transaction_ids.is_empty(), "Aborted transactions found in the block (from speculation)"); - - // Ensure the ratifications after speculation match. - if block.ratifications() != &ratifications { - bail!("The ratifications after speculation do not match the ratifications in the block"); - } - // Ensure the transactions after speculation match. - if block.transactions() != &confirmed_transactions { - bail!("The transactions after speculation do not match the transactions in the block"); - } + // Ensure speculation over the unconfirmed transactions is correct. + let ratified_finalize_operations = + self.vm.check_speculate(state, block.ratifications(), block.solutions(), block.transactions())?; // Ensure the block is correct. block.verify( diff --git a/ledger/src/check_transaction_basic.rs b/ledger/src/check_transaction_basic.rs index 0b030aab52..78a97f0637 100644 --- a/ledger/src/check_transaction_basic.rs +++ b/ledger/src/check_transaction_basic.rs @@ -16,40 +16,7 @@ use super::*; impl> Ledger { /// Checks the given transaction is well-formed and unique. - pub fn check_transaction_basic(&self, transaction: &Transaction, rejected_id: Option>) -> Result<()> { - let transaction_id = transaction.id(); - - /* Fee */ - - // If the transaction contains only 1 transition, and the transition is a split, then the fee can be skipped. - let is_fee_required = match transaction.execution() { - Some(execution) => !(execution.len() == 1 && transaction.contains_split()), - None => true, - }; - - if is_fee_required { - // Retrieve the transaction base fee. - let base_fee_amount = *transaction.base_fee_amount()?; - // Retrieve the minimum cost of the transaction. - let (cost, _) = match transaction { - // Compute the deployment cost. - Transaction::Deploy(_, _, deployment, _) => synthesizer::deployment_cost(deployment)?, - // Compute the execution cost. - Transaction::Execute(_, execution, _) => synthesizer::execution_cost(self.vm(), execution)?, - // TODO (howardwu): Plug in the Rejected struct, to compute the cost. - Transaction::Fee(_, _) => (0, (0, 0)), - }; - // Ensure the transaction has a sufficient fee. - if cost > base_fee_amount { - bail!("Transaction '{transaction_id}' has an insufficient fee - expected at least {cost} microcredits") - } - } - - /* Transaction */ - - // Ensure the transaction is valid. - self.vm().check_transaction(transaction, rejected_id)?; - - Ok(()) + pub fn check_transaction_basic(&self, transaction: &Transaction, rejected: Option<&Rejected>) -> Result<()> { + self.vm().check_transaction(transaction, rejected) } } diff --git a/ledger/test-helpers/src/lib.rs b/ledger/test-helpers/src/lib.rs index 5d40bee5dd..f2fe5a1df4 100644 --- a/ledger/test-helpers/src/lib.rs +++ b/ledger/test-helpers/src/lib.rs @@ -191,10 +191,10 @@ pub fn sample_fee_private(deployment_or_execution_id: Field, rng let credits = transaction.records().next().unwrap().1.clone(); // Decrypt the record. let credits = credits.decrypt(&private_key.try_into().unwrap()).unwrap(); - // Set the base fee amount. - let base_fee = 10_000_000; - // Set the priority fee amount. - let priority_fee = 1_000; + // Sample a base fee in microcredits. + let base_fee_in_microcredits = rng.gen_range(1_000_000..u64::MAX / 2); + // Sample a priority fee in microcredits. + let priority_fee_in_microcredits = rng.gen_range(0..u64::MAX / 2); // Initialize the process. let process = Process::load().unwrap(); @@ -203,8 +203,8 @@ pub fn sample_fee_private(deployment_or_execution_id: Field, rng .authorize_fee_private::( &private_key, credits, - base_fee, - priority_fee, + base_fee_in_microcredits, + priority_fee_in_microcredits, deployment_or_execution_id, rng, ) @@ -245,16 +245,22 @@ pub fn sample_fee_public_hardcoded(rng: &mut TestRng) -> Fee { pub fn sample_fee_public(deployment_or_execution_id: Field, rng: &mut TestRng) -> Fee { // Sample the genesis block, transaction, and private key. let (block, _, private_key) = crate::sample_genesis_block_and_components(rng); - // Set the base fee amount. - let base_fee = 10_000_000; - // Set the priority fee amount. - let priority_fee = 1_000; + // Sample a base fee in microcredits. + let base_fee_in_microcredits = rng.gen_range(1_000_000..u64::MAX / 2); + // Sample a priority fee in microcredits. + let priority_fee_in_microcredits = rng.gen_range(0..u64::MAX / 2); // Initialize the process. let process = Process::load().unwrap(); // Authorize the fee. let authorization = process - .authorize_fee_public::(&private_key, base_fee, priority_fee, deployment_or_execution_id, rng) + .authorize_fee_public::( + &private_key, + base_fee_in_microcredits, + priority_fee_in_microcredits, + deployment_or_execution_id, + rng, + ) .unwrap(); // Construct the fee trace. let (_, mut trace) = process.execute::(authorization).unwrap(); diff --git a/synthesizer/process/src/authorize.rs b/synthesizer/process/src/authorize.rs index 8d97d060e8..da526e7b5b 100644 --- a/synthesizer/process/src/authorize.rs +++ b/synthesizer/process/src/authorize.rs @@ -49,7 +49,10 @@ impl Process { let function_name = Identifier::from_str("fee_private")?; // Ensure the record contains a sufficient balance to pay the fee. - ensure_record_balance(&credits, base_fee_in_microcredits.saturating_add(priority_fee_in_microcredits))?; + ensure_record_microcredits_is_sufficient( + &credits, + base_fee_in_microcredits.saturating_add(priority_fee_in_microcredits), + )?; // Construct the inputs. let inputs = [ @@ -105,7 +108,10 @@ impl Process { } /// Ensures the record contains a sufficient balance to pay the fee. -fn ensure_record_balance(record: &Record>, fee_in_microcredits: u64) -> Result<()> { +fn ensure_record_microcredits_is_sufficient( + record: &Record>, + fee_in_microcredits: u64, +) -> Result<()> { // Retrieve the balance from the record. let balance = match record.find(&[Identifier::from_str("microcredits")?]) { Ok(console::program::Entry::Private(Plaintext::Literal(Literal::U64(amount), _))) => *amount, diff --git a/synthesizer/process/src/execute.rs b/synthesizer/process/src/execute.rs index 38ae647fa2..1e4bd132fa 100644 --- a/synthesizer/process/src/execute.rs +++ b/synthesizer/process/src/execute.rs @@ -71,19 +71,20 @@ mod tests { // Sample a private key. let private_key = PrivateKey::::new(rng).unwrap(); let owner = Address::try_from(private_key).unwrap(); + // Sample a base fee in microcredits. - let base_fee_in_microcredits = rng.gen_range(0..u64::MAX / 2); + let base_fee_in_microcredits = rng.gen_range(1_000_000..u64::MAX / 2); // Sample a priority fee in microcredits. let priority_fee_in_microcredits = rng.gen_range(0..u64::MAX / 2); - // Compute the total fee in microcredits. - let total_fee = base_fee_in_microcredits.saturating_add(priority_fee_in_microcredits); + // Sample a deployment or execution ID. + let deployment_or_execution_id = Field::rand(rng); + // Sample a credits record. + let fee_in_microcredits = base_fee_in_microcredits.saturating_add(priority_fee_in_microcredits); let credits = Record::>::from_str(&format!( - "{{ owner: {owner}.private, microcredits: {total_fee}u64.private, _nonce: 0group.public }}" + "{{ owner: {owner}.private, microcredits: {fee_in_microcredits}u64.private, _nonce: 0group.public }}" )) .unwrap(); - // Sample a deployment or execution ID. - let deployment_or_execution_id = Field::rand(rng); // Initialize the authorization. let authorization = process @@ -122,7 +123,7 @@ mod tests { // Sample a private key. let private_key = PrivateKey::new(rng).unwrap(); // Sample a base fee in microcredits. - let base_fee_in_microcredits = rng.gen_range(0..u64::MAX / 2); + let base_fee_in_microcredits = rng.gen_range(1_000_000..u64::MAX / 2); // Sample a priority fee in microcredits. let priority_fee_in_microcredits = rng.gen_range(0..u64::MAX / 2); // Sample a deployment or execution ID. diff --git a/synthesizer/process/src/stack/authorization/mod.rs b/synthesizer/process/src/stack/authorization/mod.rs index ae7d59e5b4..ec1df28e24 100644 --- a/synthesizer/process/src/stack/authorization/mod.rs +++ b/synthesizer/process/src/stack/authorization/mod.rs @@ -258,7 +258,7 @@ pub(crate) mod test_helpers { // Sample a private key. let private_key = PrivateKey::new(rng).unwrap(); // Sample a base fee in microcredits. - let base_fee_in_microcredits = rng.gen_range(0..u64::MAX / 2); + let base_fee_in_microcredits = rng.gen_range(1_000_000..u64::MAX / 2); // Sample a priority fee in microcredits. let priority_fee_in_microcredits = rng.gen_range(0..u64::MAX / 2); // Sample a deployment or execution ID. diff --git a/synthesizer/process/src/tests/test_execute.rs b/synthesizer/process/src/tests/test_execute.rs index 01c7f30c10..11f537ab88 100644 --- a/synthesizer/process/src/tests/test_execute.rs +++ b/synthesizer/process/src/tests/test_execute.rs @@ -73,11 +73,17 @@ pub fn sample_fee, B: BlockStorage, P: Final // Update the public balance in finalize storage. finalize_store.update_key_value(program_id, account_mapping, key, value).unwrap(); + // Sample a base fee in microcredits. + let base_fee_in_microcredits = rng.gen_range(1_000_000..u64::MAX / 2); + // Sample a priority fee in microcredits. + let priority_fee_in_microcredits = rng.gen_range(0..u64::MAX / 2); // Sample a dummy ID. let id = Field::rand(rng); // Authorize the fee. - let authorization = process.authorize_fee_public::(&private_key, 100, 0, id, rng).unwrap(); + let authorization = process + .authorize_fee_public::(&private_key, base_fee_in_microcredits, priority_fee_in_microcredits, id, rng) + .unwrap(); // Execute the fee. let (_, mut trace) = process.execute::(authorization).unwrap(); // Prepare the assignments. diff --git a/synthesizer/src/vm/finalize.rs b/synthesizer/src/vm/finalize.rs index 5751d67006..908ddb95b3 100644 --- a/synthesizer/src/vm/finalize.rs +++ b/synthesizer/src/vm/finalize.rs @@ -19,19 +19,32 @@ impl> VM { /// /// Returns the confirmed transactions, aborted transaction IDs, /// and finalize operations from pre-ratify and post-ratify. + /// + /// Note: This method is used to create a new block (including the genesis block). + /// - If `coinbase_reward = None`, then the `ratifications` will not be modified. + /// - If `coinbase_reward = Some(coinbase_reward)`, then the method will append a + /// `Ratify::BlockReward(block_reward)` and `Ratify::PuzzleReward(puzzle_reward)` + /// to the front of the `ratifications` list. #[inline] pub fn speculate<'a>( &self, state: FinalizeGlobalState, - ratifications: &[Ratify], - solutions: Option<&CoinbaseSolution>, - transactions: impl ExactSizeIterator>, + coinbase_reward: Option, + candidate_ratifications: Vec>, + candidate_solutions: Option<&CoinbaseSolution>, + candidate_transactions: impl ExactSizeIterator>, ) -> Result<(Ratifications, Transactions, Vec, Vec>)> { let timer = timer!("VM::speculate"); // Performs a **dry-run** over the list of ratifications, solutions, and transactions. - let (confirmed_transactions, aborted_transactions, ratified_finalize_operations) = - self.atomic_speculate(state, ratifications, solutions, transactions)?; + let (ratifications, confirmed_transactions, aborted_transactions, ratified_finalize_operations) = self + .atomic_speculate( + state, + coinbase_reward, + candidate_ratifications, + candidate_solutions, + candidate_transactions, + )?; // Convert the aborted transactions into aborted transaction IDs. let mut aborted_transaction_ids = Vec::with_capacity(aborted_transactions.len()); @@ -42,15 +55,57 @@ impl> VM { finish!(timer, "Finished dry-run of the transactions"); - // Return the transactions. + // Return the ratifications, confirmed transactions, aborted transaction IDs, and ratified finalize operations. Ok(( - Ratifications::try_from(ratifications)?, + ratifications, confirmed_transactions.into_iter().collect(), aborted_transaction_ids, ratified_finalize_operations, )) } + /// Checks the speculation on the given transactions in the VM. + /// + /// Returns the finalize operations from pre-ratify and post-ratify. + #[inline] + pub fn check_speculate( + &self, + state: FinalizeGlobalState, + ratifications: &Ratifications, + solutions: Option<&CoinbaseSolution>, + transactions: &Transactions, + ) -> Result>> { + let timer = timer!("VM::check_speculate"); + + // Reconstruct the candidate ratifications to verify the speculation. + let candidate_ratifications = ratifications.iter().cloned().collect::>(); + // Reconstruct the unconfirmed transactions to verify the speculation. + let candidate_transactions = + transactions.iter().map(|confirmed| confirmed.to_unconfirmed_transaction()).collect::>>()?; + + // Performs a **dry-run** over the list of ratifications, solutions, and transactions. + let (speculate_ratifications, confirmed_transactions, aborted_transactions, ratified_finalize_operations) = + self.atomic_speculate(state, None, candidate_ratifications, solutions, candidate_transactions.iter())?; + + // Ensure the ratifications after speculation match. + if ratifications != &speculate_ratifications { + bail!("The ratifications after speculation do not match the ratifications in the block"); + } + // Ensure the transactions after speculation match. + if transactions != &confirmed_transactions.into_iter().collect() { + bail!("The transactions after speculation do not match the transactions in the block"); + } + // Ensure there are no aborted transaction IDs from this speculation. + // Note: There should be no aborted transactions, because we are checking a block, + // where any aborted transactions should be in the aborted transaction ID list, not in transactions. + ensure!(aborted_transactions.is_empty(), "Aborted transactions found in the block (from speculation)"); + + finish!(timer, "Finished dry-run of the transactions"); + + // Return the ratified finalize operations. + Ok(ratified_finalize_operations) + } + /// Finalizes the given transactions into the VM. /// /// Returns the finalize operations from pre-ratify and post-ratify. @@ -75,15 +130,27 @@ impl> VM { impl> VM { /// Performs atomic speculation over a list of transactions. /// - /// Returns the confirmed transactions, aborted transactions, + /// Returns the ratifications, confirmed transactions, aborted transactions, /// and finalize operations from pre-ratify and post-ratify. + /// + /// Note: This method is used by `VM::speculate` and `VM::check_speculate`. + /// - If `coinbase_reward = None`, then the `ratifications` will not be modified. + /// - If `coinbase_reward = Some(coinbase_reward)`, then the method will append a + /// `Ratify::BlockReward(block_reward)` and `Ratify::PuzzleReward(puzzle_reward)` + /// to the front of the `ratifications` list. fn atomic_speculate<'a>( &self, state: FinalizeGlobalState, - ratifications: &[Ratify], + coinbase_reward: Option, + ratifications: Vec>, solutions: Option<&CoinbaseSolution>, transactions: impl ExactSizeIterator>, - ) -> Result<(Vec>, Vec<(Transaction, String)>, Vec>)> { + ) -> Result<( + Ratifications, + Vec>, + Vec<(Transaction, String)>, + Vec>, + )> { let timer = timer!("VM::atomic_speculate"); // Retrieve the number of transactions. @@ -249,6 +316,39 @@ impl> VM { /* Perform the ratifications after finalize. */ + // Prepare the reward ratifications, if any. + let reward_ratifications = match coinbase_reward { + // If the coinbase reward is `None`, then there are no reward ratifications. + None => vec![], + // If the coinbase reward is `Some(coinbase_reward)`, then we must compute the reward ratifications. + Some(coinbase_reward) => { + // Calculate the transaction fees. + let Ok(transaction_fees) = + confirmed.iter().map(|tx| Ok(*tx.priority_fee_amount()?)).sum::>() + else { + // Note: This will abort the entire atomic batch. + return Err("Failed to calculate the transaction fees during speculation".to_string()); + }; + + // Compute the block reward. + let block_reward = ledger_block::block_reward( + N::STARTING_SUPPLY, + N::BLOCK_TIME, + coinbase_reward, + transaction_fees, + ); + // Compute the puzzle reward. + let puzzle_reward = ledger_block::puzzle_reward(coinbase_reward); + + // Output the reward ratifications. + vec![Ratify::BlockReward(block_reward), Ratify::PuzzleReward(puzzle_reward)] + } + }; + + // Update the post-ratifications iterator. + let post_ratifications = reward_ratifications.iter().chain(post_ratifications); + + // Process the post-ratifications. match Self::atomic_post_ratify(store, state, post_ratifications, solutions) { // Store the finalize operations from the post-ratify. Ok(operations) => ratified_finalize_operations.extend(operations), @@ -256,11 +356,20 @@ impl> VM { Err(e) => return Err(format!("Failed to post-ratify - {e}")), } + /* Construct the ratifications after speculation. */ + + let Ok(ratifications) = + Ratifications::try_from_iter(reward_ratifications.into_iter().chain(ratifications.into_iter())) + else { + // Note: This will abort the entire atomic batch. + return Err("Failed to construct the ratifications after speculation".to_string()); + }; + finish!(timer); - // On return, 'atomic_finalize!' will abort the batch, and return the confirmed & aborted transactions, - // along with the finalize operations from pre-ratify and post-ratify. - Ok((confirmed, aborted, ratified_finalize_operations)) + // On return, 'atomic_finalize!' will abort the batch, and return the ratifications, + // confirmed & aborted transactions, and finalize operations from pre-ratify and post-ratify. + Ok((ratifications, confirmed, aborted, ratified_finalize_operations)) }) } @@ -619,11 +728,19 @@ impl> VM { // Initialize a list of finalize operations. let mut finalize_operations = Vec::new(); + // Initialize a flag for the block reward ratification. + let mut is_block_reward_ratified = false; + // Initialize a flag for the puzzle reward ratification. + let mut is_puzzle_reward_ratified = false; + // Iterate over the ratifications. for ratify in post_ratifications { match ratify { Ratify::Genesis(..) => continue, Ratify::BlockReward(block_reward) => { + // Ensure the block reward has not been ratified yet. + ensure!(!is_block_reward_ratified, "Ratify::BlockReward(..) has already been ratified"); + // Retrieve the committee mapping from storage. let current_committee_map = store.get_mapping_speculative(program_id, committee_mapping)?; // Convert the committee mapping into a committee. @@ -653,8 +770,14 @@ impl> VM { // Replace the bonded mapping in storage. store.replace_mapping(program_id, bonded_mapping, next_bonded_map)?, ]); + + // Set the block reward ratification flag. + is_block_reward_ratified = true; } Ratify::PuzzleReward(puzzle_reward) => { + // Ensure the puzzle reward has not been ratified yet. + ensure!(!is_puzzle_reward_ratified, "Ratify::PuzzleReward(..) has already been ratified"); + // If the puzzle reward is zero, skip. if *puzzle_reward == 0 { continue; @@ -686,6 +809,9 @@ impl> VM { let operation = store.update_key_value(program_id, account_mapping, key, next_value)?; finalize_operations.push(operation); } + + // Set the puzzle reward ratification flag. + is_puzzle_reward_ratified = true; } } } @@ -791,7 +917,7 @@ finalize transfer_public: ) -> Result> { // Speculate on the candidate ratifications, solutions, and transactions. let (ratifications, transactions, aborted_transaction_ids, ratified_finalize_operations) = - vm.speculate(sample_finalize_state(1), &[], None, transactions.iter())?; + vm.speculate(sample_finalize_state(1), Some(0u64), vec![], None, transactions.iter())?; assert!(aborted_transaction_ids.is_empty()); // Construct the metadata associated with the block. @@ -975,8 +1101,9 @@ finalize transfer_public: let program_id = ProgramID::from_str("testing.aleo").unwrap(); // Prepare the confirmed transactions. - let (ratifications, confirmed_transactions, aborted_transaction_ids, _) = - vm.speculate(sample_finalize_state(1), &[], None, [deployment_transaction.clone()].iter()).unwrap(); + let (ratifications, confirmed_transactions, aborted_transaction_ids, _) = vm + .speculate(sample_finalize_state(1), Some(0u64), vec![], None, [deployment_transaction.clone()].iter()) + .unwrap(); assert_eq!(confirmed_transactions.len(), 1); assert!(aborted_transaction_ids.is_empty()); @@ -996,8 +1123,9 @@ finalize transfer_public: assert!(vm.contains_program(&program_id)); // Ensure the dry run of the redeployment will cause a reject transaction to be created. - let (candidate_transactions, aborted_transaction_ids, _) = - vm.atomic_speculate(sample_finalize_state(1), &[], None, [deployment_transaction].iter()).unwrap(); + let (_, candidate_transactions, aborted_transaction_ids, _) = vm + .atomic_speculate(sample_finalize_state(1), Some(0), vec![], None, [deployment_transaction].iter()) + .unwrap(); assert_eq!(candidate_transactions.len(), 1); assert!(matches!(candidate_transactions[0], ConfirmedTransaction::RejectedDeploy(..))); assert!(aborted_transaction_ids.is_empty()); @@ -1098,8 +1226,8 @@ finalize transfer_public: // Transfer_20 -> Balance = 20 - 20 = 0 { let transactions = [mint_10.clone(), transfer_10.clone(), transfer_20.clone()]; - let (confirmed_transactions, aborted_transaction_ids, _) = - vm.atomic_speculate(sample_finalize_state(1), &[], None, transactions.iter()).unwrap(); + let (_, confirmed_transactions, aborted_transaction_ids, _) = + vm.atomic_speculate(sample_finalize_state(1), Some(0), vec![], None, transactions.iter()).unwrap(); // Assert that all the transactions are accepted. assert_eq!(confirmed_transactions.len(), 3); @@ -1118,8 +1246,8 @@ finalize transfer_public: // Transfer_30 -> Balance = 30 - 30 = 0 { let transactions = [transfer_20.clone(), mint_10.clone(), mint_20.clone(), transfer_30.clone()]; - let (confirmed_transactions, aborted_transaction_ids, _) = - vm.atomic_speculate(sample_finalize_state(1), &[], None, transactions.iter()).unwrap(); + let (_, confirmed_transactions, aborted_transaction_ids, _) = + vm.atomic_speculate(sample_finalize_state(1), Some(0), vec![], None, transactions.iter()).unwrap(); // Assert that all the transactions are accepted. assert_eq!(confirmed_transactions.len(), 4); @@ -1138,8 +1266,8 @@ finalize transfer_public: // Transfer_10 -> Balance = 0 - 10 = -10 (should be rejected) { let transactions = [transfer_20.clone(), transfer_10.clone()]; - let (confirmed_transactions, aborted_transaction_ids, _) = - vm.atomic_speculate(sample_finalize_state(1), &[], None, transactions.iter()).unwrap(); + let (_, confirmed_transactions, aborted_transaction_ids, _) = + vm.atomic_speculate(sample_finalize_state(1), Some(0), vec![], None, transactions.iter()).unwrap(); // Assert that the accepted and rejected transactions are correct. assert_eq!(confirmed_transactions.len(), 2); @@ -1162,8 +1290,8 @@ finalize transfer_public: // Transfer_10 -> Balance = 10 - 10 = 0 { let transactions = [mint_20.clone(), transfer_30.clone(), transfer_20.clone(), transfer_10.clone()]; - let (confirmed_transactions, aborted_transaction_ids, _) = - vm.atomic_speculate(sample_finalize_state(1), &[], None, transactions.iter()).unwrap(); + let (_, confirmed_transactions, aborted_transaction_ids, _) = + vm.atomic_speculate(sample_finalize_state(1), Some(0), vec![], None, transactions.iter()).unwrap(); // Assert that the accepted and rejected transactions are correct. assert_eq!(confirmed_transactions.len(), 4); @@ -1262,7 +1390,7 @@ function ped_hash: // Speculatively execute the transaction. Ensure that this call does not panic and returns a rejected transaction. let (_, confirmed_transactions, aborted_transaction_ids, _) = - vm.speculate(sample_finalize_state(1), &[], None, [transaction.clone()].iter()).unwrap(); + vm.speculate(sample_finalize_state(1), Some(0u64), vec![], None, [transaction.clone()].iter()).unwrap(); assert!(aborted_transaction_ids.is_empty()); // Ensure that the transaction is rejected. diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index 3f4386384e..b0c763207d 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -243,7 +243,7 @@ impl> VM { let state = FinalizeGlobalState::new_genesis::()?; // Speculate on the ratifications, solutions, and transactions. let (ratifications, transactions, aborted_transaction_ids, ratified_finalize_operations) = - self.speculate(state, &ratifications, solutions.as_ref(), transactions.iter())?; + self.speculate(state, None, ratifications, solutions.as_ref(), transactions.iter())?; ensure!( aborted_transaction_ids.is_empty(), "Failed to initialize a genesis block - found aborted transaction IDs" @@ -590,7 +590,7 @@ function compute: // Construct the new block header. let (ratifications, transactions, aborted_transaction_ids, ratified_finalize_operations) = - vm.speculate(sample_finalize_state(1), &[], None, transactions.iter())?; + vm.speculate(sample_finalize_state(1), Some(0u64), vec![], None, transactions.iter())?; assert!(aborted_transaction_ids.is_empty()); // Construct the metadata associated with the block. diff --git a/synthesizer/src/vm/verify.rs b/synthesizer/src/vm/verify.rs index 4163299c87..0a06b5350a 100644 --- a/synthesizer/src/vm/verify.rs +++ b/synthesizer/src/vm/verify.rs @@ -33,28 +33,13 @@ macro_rules! ensure_is_unique { impl> VM { /// Returns `true` if the transaction is valid. - pub fn verify_transaction(&self, transaction: &Transaction, rejected_id: Option>) -> bool { - self.check_transaction(transaction, rejected_id).map_err(|error| warn!("{error}")).is_ok() - } - - /// Returns `true` if the deployment is valid. - pub fn verify_deployment(&self, deployment: &Deployment) -> bool { - self.check_deployment(deployment).map_err(|error| warn!("{error}")).is_ok() - } - - /// Returns `true` if the execution is valid. - pub fn verify_execution(&self, execution: &Execution) -> bool { - self.check_execution(execution).map_err(|error| warn!("{error}")).is_ok() - } - - /// Returns `true` if the fee is valid. - pub fn verify_fee(&self, fee: &Fee, deployment_or_execution_id: Field) -> bool { - self.check_fee(fee, deployment_or_execution_id).map_err(|error| warn!("{error}")).is_ok() + pub fn verify_transaction(&self, transaction: &Transaction, rejected: Option<&Rejected>) -> bool { + self.check_transaction(transaction, rejected).map_err(|error| warn!("{error}")).is_ok() } /// Verifies the transaction in the VM. On failure, returns an error. #[inline] - pub fn check_transaction(&self, transaction: &Transaction, rejected_id: Option>) -> Result<()> { + pub fn check_transaction(&self, transaction: &Transaction, rejected: Option<&Rejected>) -> Result<()> { let timer = timer!("VM::check_transaction"); /* Transaction */ @@ -109,10 +94,12 @@ impl> VM { lap!(timer, "Check for duplicate elements"); + // First, verify the fee. + self.check_fee(transaction, rejected)?; + + // Next, verify the deployment or execution. match transaction { - Transaction::Deploy(id, owner, deployment, fee) => { - // Ensure the rejected ID is not present. - ensure!(rejected_id.is_none(), "Transaction should not have a rejected ID (deployment)"); + 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 deployment transaction '{id}'") @@ -120,52 +107,103 @@ impl> VM { // Verify the signature corresponds to the transaction ID. ensure!(owner.verify(deployment_id), "Invalid owner signature for deployment transaction '{id}'"); // Ensure the edition is correct. - ensure!(deployment.edition() == N::EDITION, "Invalid deployment: expected edition {}", N::EDITION); + if deployment.edition() != N::EDITION { + bail!("Invalid deployment transaction '{id}' - expected edition {}", N::EDITION) + } // Ensure the program ID does not already exist.. if self.transaction_store().contains_program_id(deployment.program_id())? { bail!("Program ID '{}' is already deployed", deployment.program_id()) } - // Verify the fee. - self.check_fee(fee, deployment_id)?; // Verify the deployment. - self.check_deployment(deployment)?; + self.check_deployment_internal(deployment)?; + } + Transaction::Execute(_, execution, _) => { + // Verify the execution. + self.check_execution_internal(execution)?; + } + Transaction::Fee(..) => { /* no-op */ } + } + + finish!(timer, "Verify the transaction"); + Ok(()) + } + + /// Verifies the `fee` in the given transaction. On failure, returns an error. + #[inline] + pub fn check_fee(&self, transaction: &Transaction, rejected: Option<&Rejected>) -> Result<()> { + match transaction { + Transaction::Deploy(id, _, deployment, fee) => { + // Ensure the rejected object is not present. + ensure!(rejected.is_none(), "Transaction '{id}' should not have a rejected object (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 deployment cost. + let (cost, _) = deployment_cost(deployment)?; + // Ensure the fee is sufficient to cover the cost. + if *fee.base_amount()? < cost { + bail!("Transaction '{id}' has an insufficient fee (deployment) - requires {cost} microcredits") + } + // Verify the fee. + self.check_fee_internal(fee, deployment_id)?; } Transaction::Execute(id, execution, fee) => { - // Ensure the rejected ID is not present. - ensure!(rejected_id.is_none(), "Transaction should not have a rejected ID (execution)"); + // Ensure the rejected object is not present. + ensure!(rejected.is_none(), "Transaction '{id}' should not have a rejected object (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}'") }; // Verify the fee. if let Some(fee) = fee { - self.check_fee(fee, execution_id)?; + // Compute the execution cost. + let (cost, _) = execution_cost(self, execution)?; + // Ensure the fee is sufficient to cover the cost. + if *fee.base_amount()? < cost { + bail!("Transaction '{id}' has an insufficient fee (execution) - requires {cost} microcredits") + } + // Verify the fee. + self.check_fee_internal(fee, execution_id)?; } else { // If the transaction contains only 1 transition, and the transition is a split, then the fee can be skipped. let can_skip_fee = execution.len() == 1 && transaction.contains_split(); - ensure!(can_skip_fee, "Transaction is missing a fee (execution)"); + ensure!(can_skip_fee, "Transaction '{id}' is missing a fee (execution)"); } - // Verify the execution. - self.check_execution(execution)?; } - Transaction::Fee(_, fee) => { + Transaction::Fee(id, fee) => { // Ensure the fee is nonzero. - ensure!(!fee.is_zero()?, "Invalid fee (zero)"); - // Verify the fee. - match rejected_id { - Some(rejected_id) => self.check_fee(fee, rejected_id)?, - None => bail!("Transaction is missing a rejected ID (fee)"), + ensure!(!fee.is_zero()?, "Transaction '{id}' contains a fee of 0 microcredits"); + + match rejected { + Some(rejected) => { + // Retrieve the cost. + let (cost, _) = match rejected { + Rejected::Deployment(_, deployment) => deployment_cost(deployment)?, + Rejected::Execution(execution) => execution_cost(self, execution)?, + }; + // Ensure the fee is sufficient to cover the cost. + if *fee.base_amount()? < cost { + bail!("Transaction '{id}' has an insufficient fee (reject) - requires {cost} microcredits") + } + // Verify the fee. + self.check_fee_internal(fee, rejected.to_id()?)? + } + None => bail!("Transaction '{id}' is missing a rejected object (fee)"), } } } - - finish!(timer, "Verify the transaction"); Ok(()) } +} +impl> VM { /// Verifies the given deployment. On failure, returns an error. + /// + /// Note: This is an internal check only. To ensure all components of the deployment are checked, + /// use `VM::check_transaction` instead. #[inline] - fn check_deployment(&self, deployment: &Deployment) -> Result<()> { + fn check_deployment_internal(&self, deployment: &Deployment) -> Result<()> { macro_rules! logic { ($process:expr, $network:path, $aleo:path) => {{ // Prepare the deployment. @@ -183,8 +221,11 @@ impl> VM { } /// Verifies the given execution. On failure, returns an error. + /// + /// 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(&self, execution: &Execution) -> Result<()> { + fn check_execution_internal(&self, execution: &Execution) -> Result<()> { let timer = timer!("VM::check_execution"); // Verify the execution. @@ -206,8 +247,11 @@ impl> VM { } /// Verifies the given fee. On failure, returns an error. + /// + /// 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(&self, fee: &Fee, deployment_or_execution_id: Field) -> Result<()> { + fn check_fee_internal(&self, fee: &Fee, deployment_or_execution_id: Field) -> Result<()> { let timer = timer!("VM::check_fee"); // Ensure the fee does not exceed the limit. @@ -303,14 +347,12 @@ mod tests { let deployment = vm.deploy_raw(&program, rng).unwrap(); // Ensure the deployment is valid. - assert!(vm.check_deployment(&deployment).is_ok()); - assert!(vm.verify_deployment(&deployment)); + assert!(vm.check_deployment_internal(&deployment).is_ok()); // 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(); - assert!(vm.check_deployment(&deployment_transaction).is_ok()); - assert!(vm.verify_deployment(&deployment_transaction)); + assert!(vm.check_deployment_internal(&deployment_transaction).is_ok()); } #[test] @@ -330,15 +372,13 @@ mod tests { // Ensure the proof exists. assert!(execution.proof().is_some()); // Verify the execution. - assert!(vm.check_execution(&execution).is_ok()); - assert!(vm.verify_execution(&execution)); + assert!(vm.check_execution_internal(&execution).is_ok()); // 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(); - assert!(vm.check_execution(&recovered_execution).is_ok()); - assert!(vm.verify_execution(&recovered_execution)); + assert!(vm.check_execution_internal(&recovered_execution).is_ok()); } _ => panic!("Expected an execution transaction"), } @@ -364,14 +404,12 @@ mod tests { // Ensure the proof exists. assert!(fee.proof().is_some()); // Verify the fee. - assert!(vm.check_fee(&fee, execution_id).is_ok()); - assert!(vm.verify_fee(&fee, execution_id)); + assert!(vm.check_fee_internal(&fee, execution_id).is_ok()); // 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(); - assert!(vm.check_fee(&recovered_fee, execution_id).is_ok()); - assert!(vm.verify_fee(&recovered_fee, execution_id)); + assert!(vm.check_fee_internal(&recovered_fee, execution_id).is_ok()); } _ => panic!("Expected an execution with a fee"), } @@ -435,7 +473,7 @@ mod tests { // Construct the new block header. let (ratifications, transactions, aborted_transaction_ids, ratified_finalize_operations) = - vm.speculate(sample_finalize_state(1), &[], None, [deployment_transaction].iter()).unwrap(); + vm.speculate(sample_finalize_state(1), Some(0u64), vec![], None, [deployment_transaction].iter()).unwrap(); assert!(aborted_transaction_ids.is_empty()); // Construct the metadata associated with the block. diff --git a/synthesizer/tests/test_vm_execute_and_finalize.rs b/synthesizer/tests/test_vm_execute_and_finalize.rs index e03e84eb2e..31e5ff2d2e 100644 --- a/synthesizer/tests/test_vm_execute_and_finalize.rs +++ b/synthesizer/tests/test_vm_execute_and_finalize.rs @@ -99,7 +99,7 @@ fn run_test(test: &ProgramTest) -> serde_yaml::Mapping { }; let (ratifications, transactions, aborted_transaction_ids, ratified_finalize_operations) = - vm.speculate(construct_finalize_global_state(&vm), &[], None, [transaction].iter()).unwrap(); + vm.speculate(construct_finalize_global_state(&vm), Some(0u64), vec![], None, [transaction].iter()).unwrap(); assert!(aborted_transaction_ids.is_empty()); let block = construct_next_block( @@ -244,7 +244,7 @@ fn run_test(test: &ProgramTest) -> serde_yaml::Mapping { // Speculate on the ratifications, solutions, and transaction. let (ratifications, transactions, aborted_transaction_ids, ratified_finalize_operations) = match vm - .speculate(construct_finalize_global_state(&vm), &[], None, [transaction].iter()) + .speculate(construct_finalize_global_state(&vm), Some(0u64), vec![], None, [transaction].iter()) { Ok((ratifications, transactions, aborted_transaction_ids, ratified_finalize_operations)) => { result.insert( @@ -379,7 +379,7 @@ fn construct_fee_records, R: Rng + CryptoRng } let (ratifications, transactions, aborted_transaction_ids, ratified_finalize_operations) = - vm.speculate(construct_finalize_global_state(vm), &[], None, transactions.iter()).unwrap(); + vm.speculate(construct_finalize_global_state(vm), Some(0u64), vec![], None, transactions.iter()).unwrap(); assert!(aborted_transaction_ids.is_empty()); // Create a block for the fee transactions and add them to the VM. From d148086f550ebb3db7f1c3fcf4eba87847c6f721 Mon Sep 17 00:00:00 2001 From: Howard Wu <9260812+howardwu@users.noreply.github.com> Date: Fri, 20 Oct 2023 12:58:25 -0700 Subject: [PATCH 2/7] Fix check --- ledger/src/check_next_block.rs | 2 +- ledger/src/check_transaction_basic.rs | 4 +-- synthesizer/src/vm/verify.rs | 43 ++++++++++----------------- 3 files changed, 19 insertions(+), 30 deletions(-) diff --git a/ledger/src/check_next_block.rs b/ledger/src/check_next_block.rs index 578e9e88e1..e4dafb81e9 100644 --- a/ledger/src/check_next_block.rs +++ b/ledger/src/check_next_block.rs @@ -42,7 +42,7 @@ impl> Ledger { // TODO: this intermediate allocation shouldn't be necessary; this is most likely https://github.com/rust-lang/rust/issues/89418. let transactions = block.transactions().iter().collect::>(); cfg_iter!(transactions).try_for_each(|transaction| { - self.check_transaction_basic(*transaction, transaction.to_rejected()) + self.check_transaction_basic(*transaction, transaction.to_rejected_id()?) .map_err(|e| anyhow!("Invalid transaction found in the transactions list: {e}")) })?; diff --git a/ledger/src/check_transaction_basic.rs b/ledger/src/check_transaction_basic.rs index 78a97f0637..0d2218272f 100644 --- a/ledger/src/check_transaction_basic.rs +++ b/ledger/src/check_transaction_basic.rs @@ -16,7 +16,7 @@ use super::*; impl> Ledger { /// Checks the given transaction is well-formed and unique. - pub fn check_transaction_basic(&self, transaction: &Transaction, rejected: Option<&Rejected>) -> Result<()> { - self.vm().check_transaction(transaction, rejected) + pub fn check_transaction_basic(&self, transaction: &Transaction, rejected_id: Option>) -> Result<()> { + self.vm().check_transaction(transaction, rejected_id) } } diff --git a/synthesizer/src/vm/verify.rs b/synthesizer/src/vm/verify.rs index 0a06b5350a..fac8d0d5f0 100644 --- a/synthesizer/src/vm/verify.rs +++ b/synthesizer/src/vm/verify.rs @@ -33,13 +33,13 @@ macro_rules! ensure_is_unique { impl> VM { /// Returns `true` if the transaction is valid. - pub fn verify_transaction(&self, transaction: &Transaction, rejected: Option<&Rejected>) -> bool { - self.check_transaction(transaction, rejected).map_err(|error| warn!("{error}")).is_ok() + pub fn verify_transaction(&self, transaction: &Transaction, rejected_id: Option>) -> bool { + self.check_transaction(transaction, rejected_id).map_err(|error| warn!("{error}")).is_ok() } /// Verifies the transaction in the VM. On failure, returns an error. #[inline] - pub fn check_transaction(&self, transaction: &Transaction, rejected: Option<&Rejected>) -> Result<()> { + pub fn check_transaction(&self, transaction: &Transaction, rejected_id: Option>) -> Result<()> { let timer = timer!("VM::check_transaction"); /* Transaction */ @@ -95,7 +95,7 @@ impl> VM { lap!(timer, "Check for duplicate elements"); // First, verify the fee. - self.check_fee(transaction, rejected)?; + self.check_fee(transaction, rejected_id)?; // Next, verify the deployment or execution. match transaction { @@ -130,11 +130,11 @@ impl> VM { /// Verifies the `fee` in the given transaction. On failure, returns an error. #[inline] - pub fn check_fee(&self, transaction: &Transaction, rejected: Option<&Rejected>) -> Result<()> { + pub fn check_fee(&self, transaction: &Transaction, rejected_id: Option>) -> Result<()> { match transaction { Transaction::Deploy(id, _, deployment, fee) => { - // Ensure the rejected object is not present. - ensure!(rejected.is_none(), "Transaction '{id}' should not have a rejected object (deployment)"); + // 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}'") @@ -149,8 +149,8 @@ impl> VM { self.check_fee_internal(fee, deployment_id)?; } Transaction::Execute(id, execution, fee) => { - // Ensure the rejected object is not present. - ensure!(rejected.is_none(), "Transaction '{id}' should not have a rejected object (execution)"); + // 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}'") @@ -171,25 +171,14 @@ impl> VM { ensure!(can_skip_fee, "Transaction '{id}' is missing a fee (execution)"); } } + // Note: This transaction type does not need to check the fee amount, because: + // 1. The fee is guaranteed to be non-zero by the constructor of `Transaction::Fee`. + // 2. The fee may be less that the deployment or execution cost, as this is a valid reason it was rejected. Transaction::Fee(id, fee) => { - // Ensure the fee is nonzero. - ensure!(!fee.is_zero()?, "Transaction '{id}' contains a fee of 0 microcredits"); - - match rejected { - Some(rejected) => { - // Retrieve the cost. - let (cost, _) = match rejected { - Rejected::Deployment(_, deployment) => deployment_cost(deployment)?, - Rejected::Execution(execution) => execution_cost(self, execution)?, - }; - // Ensure the fee is sufficient to cover the cost. - if *fee.base_amount()? < cost { - bail!("Transaction '{id}' has an insufficient fee (reject) - requires {cost} microcredits") - } - // Verify the fee. - self.check_fee_internal(fee, rejected.to_id()?)? - } - None => bail!("Transaction '{id}' is missing a rejected object (fee)"), + // Verify the fee. + match rejected_id { + Some(rejected_id) => self.check_fee_internal(fee, rejected_id)?, + None => bail!("Transaction '{id}' is missing a rejected ID (fee)"), } } } From 6e31d5c9ce2be184db2e63c86061117a6111da4c Mon Sep 17 00:00:00 2001 From: Howard Wu <9260812+howardwu@users.noreply.github.com> Date: Fri, 20 Oct 2023 13:03:27 -0700 Subject: [PATCH 3/7] Fix tests --- ledger/block/src/transaction/fee/mod.rs | 4 ++-- ledger/test-helpers/src/lib.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ledger/block/src/transaction/fee/mod.rs b/ledger/block/src/transaction/fee/mod.rs index e6e114bbea..8ee356c9fb 100644 --- a/ledger/block/src/transaction/fee/mod.rs +++ b/ledger/block/src/transaction/fee/mod.rs @@ -235,9 +235,9 @@ pub mod test_helpers { // Decrypt the record. let credits = credits.decrypt(&private_key.try_into().unwrap()).unwrap(); // Sample a base fee in microcredits. - let base_fee_in_microcredits = rng.gen_range(1_000_000..u64::MAX / 2); + let base_fee_in_microcredits = 10_000_000; // Sample a priority fee in microcredits. - let priority_fee_in_microcredits = rng.gen_range(0..u64::MAX / 2); + let priority_fee_in_microcredits = 1_000; // Initialize the process. let process = Process::load().unwrap(); diff --git a/ledger/test-helpers/src/lib.rs b/ledger/test-helpers/src/lib.rs index f2fe5a1df4..3fde04d18e 100644 --- a/ledger/test-helpers/src/lib.rs +++ b/ledger/test-helpers/src/lib.rs @@ -192,9 +192,9 @@ pub fn sample_fee_private(deployment_or_execution_id: Field, rng // Decrypt the record. let credits = credits.decrypt(&private_key.try_into().unwrap()).unwrap(); // Sample a base fee in microcredits. - let base_fee_in_microcredits = rng.gen_range(1_000_000..u64::MAX / 2); + let base_fee_in_microcredits = 10_000_000; // Sample a priority fee in microcredits. - let priority_fee_in_microcredits = rng.gen_range(0..u64::MAX / 2); + let priority_fee_in_microcredits = 1_000; // Initialize the process. let process = Process::load().unwrap(); From d891ae8c7b6b881ef186c91764e9deaa4436da6e Mon Sep 17 00:00:00 2001 From: Howard Wu <9260812+howardwu@users.noreply.github.com> Date: Fri, 20 Oct 2023 13:25:56 -0700 Subject: [PATCH 4/7] Fix test --- ledger/test-helpers/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ledger/test-helpers/src/lib.rs b/ledger/test-helpers/src/lib.rs index 3fde04d18e..81afab9fda 100644 --- a/ledger/test-helpers/src/lib.rs +++ b/ledger/test-helpers/src/lib.rs @@ -246,9 +246,9 @@ pub fn sample_fee_public(deployment_or_execution_id: Field, rng: // Sample the genesis block, transaction, and private key. let (block, _, private_key) = crate::sample_genesis_block_and_components(rng); // Sample a base fee in microcredits. - let base_fee_in_microcredits = rng.gen_range(1_000_000..u64::MAX / 2); + let base_fee_in_microcredits = 10_000_000; // Sample a priority fee in microcredits. - let priority_fee_in_microcredits = rng.gen_range(0..u64::MAX / 2); + let priority_fee_in_microcredits = 1_000; // Initialize the process. let process = Process::load().unwrap(); From fd25349077293296607ffe1b5c084f0939a5b479 Mon Sep 17 00:00:00 2001 From: Howard Wu <9260812+howardwu@users.noreply.github.com> Date: Fri, 20 Oct 2023 13:28:51 -0700 Subject: [PATCH 5/7] Fix test --- synthesizer/process/src/tests/test_execute.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/synthesizer/process/src/tests/test_execute.rs b/synthesizer/process/src/tests/test_execute.rs index 11f537ab88..2af2faf97e 100644 --- a/synthesizer/process/src/tests/test_execute.rs +++ b/synthesizer/process/src/tests/test_execute.rs @@ -74,9 +74,9 @@ pub fn sample_fee, B: BlockStorage, P: Final finalize_store.update_key_value(program_id, account_mapping, key, value).unwrap(); // Sample a base fee in microcredits. - let base_fee_in_microcredits = rng.gen_range(1_000_000..u64::MAX / 2); + let base_fee_in_microcredits = 100; // Sample a priority fee in microcredits. - let priority_fee_in_microcredits = rng.gen_range(0..u64::MAX / 2); + let priority_fee_in_microcredits = 0; // Sample a dummy ID. let id = Field::rand(rng); From 51e5164b9e897e25f834f3563d2b37eca3c9c671 Mon Sep 17 00:00:00 2001 From: Howard Wu <9260812+howardwu@users.noreply.github.com> Date: Fri, 20 Oct 2023 13:34:28 -0700 Subject: [PATCH 6/7] Fix tests --- synthesizer/src/vm/finalize.rs | 19 +++++++++---------- synthesizer/src/vm/mod.rs | 2 +- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/synthesizer/src/vm/finalize.rs b/synthesizer/src/vm/finalize.rs index 908ddb95b3..d370991031 100644 --- a/synthesizer/src/vm/finalize.rs +++ b/synthesizer/src/vm/finalize.rs @@ -917,7 +917,7 @@ finalize transfer_public: ) -> Result> { // Speculate on the candidate ratifications, solutions, and transactions. let (ratifications, transactions, aborted_transaction_ids, ratified_finalize_operations) = - vm.speculate(sample_finalize_state(1), Some(0u64), vec![], None, transactions.iter())?; + vm.speculate(sample_finalize_state(1), None, vec![], None, transactions.iter())?; assert!(aborted_transaction_ids.is_empty()); // Construct the metadata associated with the block. @@ -1102,7 +1102,7 @@ finalize transfer_public: // Prepare the confirmed transactions. let (ratifications, confirmed_transactions, aborted_transaction_ids, _) = vm - .speculate(sample_finalize_state(1), Some(0u64), vec![], None, [deployment_transaction.clone()].iter()) + .speculate(sample_finalize_state(1), None, vec![], None, [deployment_transaction.clone()].iter()) .unwrap(); assert_eq!(confirmed_transactions.len(), 1); assert!(aborted_transaction_ids.is_empty()); @@ -1123,9 +1123,8 @@ finalize transfer_public: assert!(vm.contains_program(&program_id)); // Ensure the dry run of the redeployment will cause a reject transaction to be created. - let (_, candidate_transactions, aborted_transaction_ids, _) = vm - .atomic_speculate(sample_finalize_state(1), Some(0), vec![], None, [deployment_transaction].iter()) - .unwrap(); + let (_, candidate_transactions, aborted_transaction_ids, _) = + vm.atomic_speculate(sample_finalize_state(1), None, vec![], None, [deployment_transaction].iter()).unwrap(); assert_eq!(candidate_transactions.len(), 1); assert!(matches!(candidate_transactions[0], ConfirmedTransaction::RejectedDeploy(..))); assert!(aborted_transaction_ids.is_empty()); @@ -1227,7 +1226,7 @@ finalize transfer_public: { let transactions = [mint_10.clone(), transfer_10.clone(), transfer_20.clone()]; let (_, confirmed_transactions, aborted_transaction_ids, _) = - vm.atomic_speculate(sample_finalize_state(1), Some(0), vec![], None, transactions.iter()).unwrap(); + vm.atomic_speculate(sample_finalize_state(1), None, vec![], None, transactions.iter()).unwrap(); // Assert that all the transactions are accepted. assert_eq!(confirmed_transactions.len(), 3); @@ -1247,7 +1246,7 @@ finalize transfer_public: { let transactions = [transfer_20.clone(), mint_10.clone(), mint_20.clone(), transfer_30.clone()]; let (_, confirmed_transactions, aborted_transaction_ids, _) = - vm.atomic_speculate(sample_finalize_state(1), Some(0), vec![], None, transactions.iter()).unwrap(); + vm.atomic_speculate(sample_finalize_state(1), None, vec![], None, transactions.iter()).unwrap(); // Assert that all the transactions are accepted. assert_eq!(confirmed_transactions.len(), 4); @@ -1267,7 +1266,7 @@ finalize transfer_public: { let transactions = [transfer_20.clone(), transfer_10.clone()]; let (_, confirmed_transactions, aborted_transaction_ids, _) = - vm.atomic_speculate(sample_finalize_state(1), Some(0), vec![], None, transactions.iter()).unwrap(); + vm.atomic_speculate(sample_finalize_state(1), None, vec![], None, transactions.iter()).unwrap(); // Assert that the accepted and rejected transactions are correct. assert_eq!(confirmed_transactions.len(), 2); @@ -1291,7 +1290,7 @@ finalize transfer_public: { let transactions = [mint_20.clone(), transfer_30.clone(), transfer_20.clone(), transfer_10.clone()]; let (_, confirmed_transactions, aborted_transaction_ids, _) = - vm.atomic_speculate(sample_finalize_state(1), Some(0), vec![], None, transactions.iter()).unwrap(); + vm.atomic_speculate(sample_finalize_state(1), None, vec![], None, transactions.iter()).unwrap(); // Assert that the accepted and rejected transactions are correct. assert_eq!(confirmed_transactions.len(), 4); @@ -1390,7 +1389,7 @@ function ped_hash: // Speculatively execute the transaction. Ensure that this call does not panic and returns a rejected transaction. let (_, confirmed_transactions, aborted_transaction_ids, _) = - vm.speculate(sample_finalize_state(1), Some(0u64), vec![], None, [transaction.clone()].iter()).unwrap(); + vm.speculate(sample_finalize_state(1), None, vec![], None, [transaction.clone()].iter()).unwrap(); assert!(aborted_transaction_ids.is_empty()); // Ensure that the transaction is rejected. diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index b0c763207d..d6068454e7 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -590,7 +590,7 @@ function compute: // Construct the new block header. let (ratifications, transactions, aborted_transaction_ids, ratified_finalize_operations) = - vm.speculate(sample_finalize_state(1), Some(0u64), vec![], None, transactions.iter())?; + vm.speculate(sample_finalize_state(1), None, vec![], None, transactions.iter())?; assert!(aborted_transaction_ids.is_empty()); // Construct the metadata associated with the block. From 1533a2b53e8d9a9ff39b721c4e183cd3c6907c06 Mon Sep 17 00:00:00 2001 From: Howard Wu <9260812+howardwu@users.noreply.github.com> Date: Fri, 20 Oct 2023 14:00:34 -0700 Subject: [PATCH 7/7] Improve test debugging, fix tests --- ledger/benches/transaction.rs | 2 +- ledger/src/tests.rs | 6 +-- synthesizer/src/vm/finalize.rs | 2 +- synthesizer/src/vm/mod.rs | 16 +++++--- synthesizer/src/vm/verify.rs | 38 +++++++------------ .../tests/test_vm_execute_and_finalize.rs | 2 +- 6 files changed, 30 insertions(+), 36 deletions(-) diff --git a/ledger/benches/transaction.rs b/ledger/benches/transaction.rs index 3fea0a959d..c37267d923 100644 --- a/ledger/benches/transaction.rs +++ b/ledger/benches/transaction.rs @@ -80,7 +80,7 @@ function hello: c.bench_function("Transaction::Deploy - verify", |b| { let transaction = vm.deploy(&private_key, &program, Some(records[0].clone()), 600000, None, rng).unwrap(); - b.iter(|| assert!(vm.verify_transaction(&transaction, None))) + b.iter(|| vm.check_transaction(&transaction, None).unwrap()) }); } diff --git a/ledger/src/tests.rs b/ledger/src/tests.rs index 5370273642..d033edef82 100644 --- a/ledger/src/tests.rs +++ b/ledger/src/tests.rs @@ -233,7 +233,7 @@ finalize foo: // Deploy. let transaction = ledger.vm.deploy(&private_key, &program, credits, 0, None, rng).unwrap(); // Verify. - assert!(ledger.vm().verify_transaction(&transaction, None)); + ledger.vm().check_transaction(&transaction, None).unwrap(); // Construct the next block. let block = @@ -287,7 +287,7 @@ finalize foo: let transaction = ledger.vm.execute(&private_key, ("dummy.aleo", "foo"), inputs, Some(sufficient_record), 0, None, rng).unwrap(); // Verify. - assert!(ledger.vm.verify_transaction(&transaction, None)); + ledger.vm.check_transaction(&transaction, None).unwrap(); // Ensure that the ledger deems the transaction valid. assert!(ledger.check_transaction_basic(&transaction, None).is_ok()); } @@ -419,7 +419,7 @@ finalize foo: // Deploy. let transaction = ledger.vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); // Verify. - assert!(ledger.vm().verify_transaction(&transaction, None)); + ledger.vm().check_transaction(&transaction, None).unwrap(); // Construct the next block. let block = diff --git a/synthesizer/src/vm/finalize.rs b/synthesizer/src/vm/finalize.rs index d370991031..c30166343a 100644 --- a/synthesizer/src/vm/finalize.rs +++ b/synthesizer/src/vm/finalize.rs @@ -1028,7 +1028,7 @@ finalize transfer_public: .execute(&caller_private_key, (program_id, function_name), inputs.into_iter(), credits, 1, None, rng) .unwrap(); // Verify. - assert!(vm.verify_transaction(&transaction, None)); + vm.check_transaction(&transaction, None).unwrap(); // Return the transaction. transaction diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index d6068454e7..bfcc0b6d5e 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -437,7 +437,7 @@ function compute: // Deploy. let transaction = vm.deploy(&caller_private_key, &program, credits, 10, None, rng).unwrap(); // Verify. - assert!(vm.verify_transaction(&transaction, None)); + vm.check_transaction(&transaction, None).unwrap(); // Return the transaction. transaction }) @@ -480,7 +480,7 @@ function compute: // Construct the execute transaction. let transaction = vm.execute_authorization(authorization, None, None, rng).unwrap(); // Verify. - assert!(vm.verify_transaction(&transaction, None)); + vm.check_transaction(&transaction, None).unwrap(); // Return the transaction. transaction }) @@ -524,7 +524,7 @@ function compute: .execute(&caller_private_key, ("credits.aleo", "transfer_public"), inputs, record, 0, None, rng) .unwrap(); // Verify. - assert!(vm.verify_transaction(&transaction, None)); + vm.check_transaction(&transaction, None).unwrap(); // Return the transaction. transaction }) @@ -562,7 +562,13 @@ function compute: // Authorize the fee. let authorization = vm - .authorize_fee_public(&caller_private_key, 100, 100, execution.to_execution_id().unwrap(), rng) + .authorize_fee_public( + &caller_private_key, + 10_000_000, + 100, + execution.to_execution_id().unwrap(), + rng, + ) .unwrap(); // Compute the fee. let fee = vm.execute_fee_authorization(authorization, None, rng).unwrap(); @@ -570,7 +576,7 @@ function compute: // Construct the transaction. let transaction = Transaction::from_execution(execution, Some(fee)).unwrap(); // Verify. - assert!(vm.verify_transaction(&transaction, None)); + vm.check_transaction(&transaction, None).unwrap(); // Return the transaction. transaction }) diff --git a/synthesizer/src/vm/verify.rs b/synthesizer/src/vm/verify.rs index fac8d0d5f0..06e2d86165 100644 --- a/synthesizer/src/vm/verify.rs +++ b/synthesizer/src/vm/verify.rs @@ -32,11 +32,6 @@ macro_rules! ensure_is_unique { } impl> VM { - /// Returns `true` if the transaction is valid. - pub fn verify_transaction(&self, transaction: &Transaction, rejected_id: Option>) -> bool { - self.check_transaction(transaction, rejected_id).map_err(|error| warn!("{error}")).is_ok() - } - /// Verifies the transaction in the VM. On failure, returns an error. #[inline] pub fn check_transaction(&self, transaction: &Transaction, rejected_id: Option>) -> Result<()> { @@ -308,20 +303,17 @@ mod tests { // Fetch a deployment transaction. let deployment_transaction = crate::vm::test_helpers::sample_deployment_transaction(rng); // Ensure the transaction verifies. - assert!(vm.check_transaction(&deployment_transaction, None).is_ok()); - assert!(vm.verify_transaction(&deployment_transaction, None)); + vm.check_transaction(&deployment_transaction, None).unwrap(); // Fetch an execution transaction. let execution_transaction = crate::vm::test_helpers::sample_execution_transaction_with_private_fee(rng); // Ensure the transaction verifies. - assert!(vm.check_transaction(&execution_transaction, None).is_ok()); - assert!(vm.verify_transaction(&execution_transaction, None)); + vm.check_transaction(&execution_transaction, None).unwrap(); // Fetch an execution transaction. let execution_transaction = crate::vm::test_helpers::sample_execution_transaction_with_public_fee(rng); // Ensure the transaction verifies. - assert!(vm.check_transaction(&execution_transaction, None).is_ok()); - assert!(vm.verify_transaction(&execution_transaction, None)); + vm.check_transaction(&execution_transaction, None).unwrap(); } #[test] @@ -336,12 +328,12 @@ mod tests { let deployment = vm.deploy_raw(&program, rng).unwrap(); // Ensure the deployment is valid. - assert!(vm.check_deployment_internal(&deployment).is_ok()); + vm.check_deployment_internal(&deployment).unwrap(); // 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(); - assert!(vm.check_deployment_internal(&deployment_transaction).is_ok()); + vm.check_deployment_internal(&deployment_transaction).unwrap(); } #[test] @@ -361,13 +353,13 @@ mod tests { // Ensure the proof exists. assert!(execution.proof().is_some()); // Verify the execution. - assert!(vm.check_execution_internal(&execution).is_ok()); + vm.check_execution_internal(&execution).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(); - assert!(vm.check_execution_internal(&recovered_execution).is_ok()); + vm.check_execution_internal(&recovered_execution).unwrap(); } _ => panic!("Expected an execution transaction"), } @@ -393,12 +385,12 @@ mod tests { // Ensure the proof exists. assert!(fee.proof().is_some()); // Verify the fee. - assert!(vm.check_fee_internal(&fee, execution_id).is_ok()); + vm.check_fee_internal(&fee, execution_id).unwrap(); // 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(); - assert!(vm.check_fee_internal(&recovered_fee, execution_id).is_ok()); + vm.check_fee_internal(&recovered_fee, execution_id).unwrap(); } _ => panic!("Expected an execution with a fee"), } @@ -418,18 +410,15 @@ 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); - assert!(vm.check_transaction(&valid_transaction, None).is_ok()); - assert!(vm.verify_transaction(&valid_transaction, None)); + vm.check_transaction(&valid_transaction, None).unwrap(); // Fetch a valid execution transaction with a public fee. let valid_transaction = crate::vm::test_helpers::sample_execution_transaction_with_public_fee(rng); - assert!(vm.check_transaction(&valid_transaction, None).is_ok()); - assert!(vm.verify_transaction(&valid_transaction, None)); + vm.check_transaction(&valid_transaction, None).unwrap(); // Fetch an valid execution transaction with no fee. let valid_transaction = crate::vm::test_helpers::sample_execution_transaction_without_fee(rng); - assert!(vm.check_transaction(&valid_transaction, None).is_ok()); - assert!(vm.verify_transaction(&valid_transaction, None)); + vm.check_transaction(&valid_transaction, None).unwrap(); } #[test] @@ -525,8 +514,7 @@ mod tests { vm.execute(&caller_private_key, ("testing.aleo", "initialize"), inputs, credits, 10, None, rng).unwrap(); // Verify. - assert!(vm.check_transaction(&transaction, None).is_ok()); - assert!(vm.verify_transaction(&transaction, None)); + vm.check_transaction(&transaction, None).unwrap(); } #[test] diff --git a/synthesizer/tests/test_vm_execute_and_finalize.rs b/synthesizer/tests/test_vm_execute_and_finalize.rs index 31e5ff2d2e..ce7ff4a93e 100644 --- a/synthesizer/tests/test_vm_execute_and_finalize.rs +++ b/synthesizer/tests/test_vm_execute_and_finalize.rs @@ -189,7 +189,7 @@ fn run_test(test: &ProgramTest) -> serde_yaml::Mapping { }; // Attempt to verify the transaction. - let verified = vm.verify_transaction(&transaction, None); + let verified = vm.check_transaction(&transaction, None).is_ok(); // Store the verification result. result.insert(serde_yaml::Value::String("verified".to_string()), serde_yaml::Value::Bool(verified));