From eac696a552aca03e629e7ab28869ddb60f6e371f Mon Sep 17 00:00:00 2001 From: raychu86 <14917648+raychu86@users.noreply.github.com> Date: Fri, 5 Apr 2024 16:42:02 -0400 Subject: [PATCH 01/11] Create helper method to check if transactions should be aborted --- synthesizer/src/vm/finalize.rs | 111 ++++++++++++++++++--------------- 1 file changed, 62 insertions(+), 49 deletions(-) diff --git a/synthesizer/src/vm/finalize.rs b/synthesizer/src/vm/finalize.rs index 7b59956b28..bd44ed3f49 100644 --- a/synthesizer/src/vm/finalize.rs +++ b/synthesizer/src/vm/finalize.rs @@ -294,55 +294,14 @@ impl> VM { continue 'outer; } - // Ensure that the transaction is not producing a duplicate transition. - for transition_id in transaction.transition_ids() { - // If the transition ID is already produced in this block or previous blocks, abort the transaction. - if transition_ids.contains(transition_id) - || self.transition_store().contains_transition_id(transition_id).unwrap_or(true) - { - // Store the aborted transaction. - aborted.push((transaction.clone(), format!("Duplicate transition {transition_id}"))); - // Continue to the next transaction. - continue 'outer; - } - } - - // Ensure that the transaction is not double-spending an input. - for input_id in transaction.input_ids() { - // If the input ID is already spent in this block or previous blocks, abort the transaction. - if input_ids.contains(input_id) - || self.transition_store().contains_input_id(input_id).unwrap_or(true) - { - // Store the aborted transaction. - aborted.push((transaction.clone(), format!("Double-spending input {input_id}"))); - // Continue to the next transaction. - continue 'outer; - } - } - - // Ensure that the transaction is not producing a duplicate output. - for output_id in transaction.output_ids() { - // If the output ID is already produced in this block or previous blocks, abort the transaction. - if output_ids.contains(output_id) - || self.transition_store().contains_output_id(output_id).unwrap_or(true) - { - // Store the aborted transaction. - aborted.push((transaction.clone(), format!("Duplicate output {output_id}"))); - // Continue to the next transaction. - continue 'outer; - } - } - - // // Ensure that the transaction is not producing a duplicate transition public key. - // // Note that the tpk and tcm are corresponding, so a uniqueness check for just the tpk is sufficient. - for tpk in transaction.transition_public_keys() { - // If the transition public key is already produced in this block or previous blocks, abort the transaction. - if tpks.contains(tpk) || self.transition_store().contains_tpk(tpk).unwrap_or(true) { - // Store the aborted transaction. - aborted.push((transaction.clone(), format!("Duplicate transition public key {tpk}"))); - // Continue to the next transaction. - continue 'outer; - } + // Determine if the transaction should be aborted. + if let Some(reason) = + self.should_abort_transaction(transaction, &transition_ids, &input_ids, &output_ids, &tpks) + { + // Store the aborted transaction. + aborted.push((transaction.clone(), reason)); + // Continue to the next transaction. + continue 'outer; } // Process the transaction in an isolated atomic batch. @@ -788,6 +747,60 @@ impl> VM { }) } + /// Returns `Some(reason)` if the transaction is aborted. Otherwise, returns `None`. + /// + /// The transaction will be aborted if any of the following conditions are met: + /// - The transaction is producing a duplicate transition + /// - The transaction is double-spending an input + /// - The transaction is producing a duplicate output + /// - The transaction is producing a duplicate transition public key + fn should_abort_transaction( + &self, + transaction: &Transaction, + transition_ids: &IndexSet, + input_ids: &IndexSet>, + output_ids: &IndexSet>, + tpks: &IndexSet>, + ) -> Option { + // Ensure that the transaction is not producing a duplicate transition. + for transition_id in transaction.transition_ids() { + // If the transition ID is already produced in this block or previous blocks, abort the transaction. + if transition_ids.contains(transition_id) + || self.transition_store().contains_transition_id(transition_id).unwrap_or(true) + { + return Some(format!("Duplicate transition {transition_id}")); + } + } + + // Ensure that the transaction is not double-spending an input. + for input_id in transaction.input_ids() { + // If the input ID is already spent in this block or previous blocks, abort the transaction. + if input_ids.contains(input_id) || self.transition_store().contains_input_id(input_id).unwrap_or(true) { + return Some(format!("Double-spending input {input_id}")); + } + } + + // Ensure that the transaction is not producing a duplicate output. + for output_id in transaction.output_ids() { + // If the output ID is already produced in this block or previous blocks, abort the transaction. + if output_ids.contains(output_id) || self.transition_store().contains_output_id(output_id).unwrap_or(true) { + return Some(format!("Duplicate output {output_id}")); + } + } + + // // Ensure that the transaction is not producing a duplicate transition public key. + // // Note that the tpk and tcm are corresponding, so a uniqueness check for just the tpk is sufficient. + for tpk in transaction.transition_public_keys() { + // If the transition public key is already produced in this block or previous blocks, abort the transaction. + if tpks.contains(tpk) || self.transition_store().contains_tpk(tpk).unwrap_or(true) { + return Some(format!("Duplicate transition public key {tpk}")); + } + } + + // Return `None` because the transaction is well-formed. + None + } + /// Performs precondition checks on the transactions prior to speculation. /// /// This method is used to check the following conditions: From b4ba2f8f054bb0a939aa560f93d95fd08594a3e7 Mon Sep 17 00:00:00 2001 From: raychu86 <14917648+raychu86@users.noreply.github.com> Date: Fri, 5 Apr 2024 17:02:57 -0400 Subject: [PATCH 02/11] Use helper method to abort transactions prior to verification --- synthesizer/src/vm/finalize.rs | 37 +++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/synthesizer/src/vm/finalize.rs b/synthesizer/src/vm/finalize.rs index bd44ed3f49..dfbdfa7880 100644 --- a/synthesizer/src/vm/finalize.rs +++ b/synthesizer/src/vm/finalize.rs @@ -815,6 +815,15 @@ impl> VM { let mut valid_transactions = Vec::with_capacity(transactions.len()); let mut aborted_transactions = Vec::with_capacity(transactions.len()); + // Initialize a list of created transition IDs. + let transition_ids: Arc>> = Default::default(); + // Initialize a list of spent input IDs. + let input_ids: Arc>>> = Default::default(); + // Initialize a list of created output IDs. + let output_ids: Arc>>> = Default::default(); + // Initialize the list of created transition public keys. + let tpks: Arc>>> = Default::default(); + // Separate the transactions into deploys and executions. let (deployments, executions): (Vec<&Transaction>, Vec<&Transaction>) = transactions.iter().partition(|tx| tx.is_deploy()); @@ -835,9 +844,35 @@ impl> VM { "Fee transactions are not allowed in speculate".to_string(), )); } + + // Determine if the transaction should be aborted. This will prevent the VM from performing + // verification on transactions that would have been aborted in `VM::atomic_speculate`. + if let Some(reason) = self.should_abort_transaction( + transaction, + &transition_ids.lock(), + &input_ids.lock(), + &output_ids.lock(), + &tpks.lock(), + ) { + // Continue to the next transaction. + return Either::Right((*transaction, reason.to_string())); + } + // Verify the transaction. match self.check_transaction(transaction, None, &mut rng) { - Ok(_) => Either::Left(*transaction), + // If the transaction is valid, add it to the list of valid transactions. + Ok(_) => { + // Add the transition IDs to the set of produced transition IDs. + transition_ids.lock().extend(transaction.transition_ids()); + // Add the input IDs to the set of spent input IDs. + input_ids.lock().extend(transaction.input_ids()); + // Add the output IDs to the set of produced output IDs. + output_ids.lock().extend(transaction.output_ids()); + // Add the transition public keys to the set of produced transition public keys. + tpks.lock().extend(transaction.transition_public_keys()); + Either::Left(*transaction) + } + // If the transaction is invalid, add it to the list of aborted transactions. Err(e) => Either::Right((*transaction, e.to_string())), } }); From 950c422a9d9fd0f7e734176d85c3fa01b438138e Mon Sep 17 00:00:00 2001 From: raychu86 <14917648+raychu86@users.noreply.github.com> Date: Mon, 8 Apr 2024 11:36:36 -0400 Subject: [PATCH 03/11] Add rule for aborting subsequent deployments by same payer in a block --- synthesizer/src/vm/finalize.rs | 40 +++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/synthesizer/src/vm/finalize.rs b/synthesizer/src/vm/finalize.rs index dfbdfa7880..1ee6474742 100644 --- a/synthesizer/src/vm/finalize.rs +++ b/synthesizer/src/vm/finalize.rs @@ -282,6 +282,8 @@ impl> VM { let mut output_ids: IndexSet> = IndexSet::new(); // Initialize the list of created transition public keys. let mut tpks: IndexSet> = IndexSet::new(); + // Initialize the list of deployment payers. + let mut deployment_payers: IndexSet> = IndexSet::new(); // Finalize the transactions. 'outer: for transaction in transactions { @@ -295,9 +297,14 @@ impl> VM { } // Determine if the transaction should be aborted. - if let Some(reason) = - self.should_abort_transaction(transaction, &transition_ids, &input_ids, &output_ids, &tpks) - { + if let Some(reason) = self.should_abort_transaction( + transaction, + &transition_ids, + &input_ids, + &output_ids, + &tpks, + &deployment_payers, + ) { // Store the aborted transaction. aborted.push((transaction.clone(), reason)); // Continue to the next transaction. @@ -428,6 +435,10 @@ impl> VM { output_ids.extend(confirmed_transaction.transaction().output_ids()); // Add the transition public keys to the set of produced transition public keys. tpks.extend(confirmed_transaction.transaction().transition_public_keys()); + // Add the deployment payer to the set of deployment payers. + if let Transaction::Deploy(_, _, _, fee) = confirmed_transaction.transaction() { + fee.payer().map(|payer| deployment_payers.insert(payer)); + } // Store the confirmed transaction. confirmed.push(confirmed_transaction); // Increment the transaction index counter. @@ -754,6 +765,7 @@ impl> VM { /// - The transaction is double-spending an input /// - The transaction is producing a duplicate output /// - The transaction is producing a duplicate transition public key + /// - The transaction is another deployment in the block from the same public fee payer. fn should_abort_transaction( &self, transaction: &Transaction, @@ -761,6 +773,7 @@ impl> VM { input_ids: &IndexSet>, output_ids: &IndexSet>, tpks: &IndexSet>, + deployment_payers: &IndexSet>, ) -> Option { // Ensure that the transaction is not producing a duplicate transition. for transition_id in transaction.transition_ids() { @@ -788,8 +801,8 @@ impl> VM { } } - // // Ensure that the transaction is not producing a duplicate transition public key. - // // Note that the tpk and tcm are corresponding, so a uniqueness check for just the tpk is sufficient. + // Ensure that the transaction is not producing a duplicate transition public key. + // Note that the tpk and tcm are corresponding, so a uniqueness check for just the tpk is sufficient. for tpk in transaction.transition_public_keys() { // If the transition public key is already produced in this block or previous blocks, abort the transaction. if tpks.contains(tpk) || self.transition_store().contains_tpk(tpk).unwrap_or(true) { @@ -797,6 +810,16 @@ impl> VM { } } + // If the transaction is a deployment, ensure that it is not another deployment in the block from the same public fee payer. + if let Transaction::Deploy(_, _, _, fee) = transaction { + // If the deployment spender has already deployed in this block, abort the transaction. + if let Some(payer) = fee.payer() { + if deployment_payers.contains(&payer) { + return Some(format!("Another deployment in the block from the same public fee payer {payer}")); + } + } + } + // Return `None` because the transaction is well-formed. None } @@ -823,6 +846,8 @@ impl> VM { let output_ids: Arc>>> = Default::default(); // Initialize the list of created transition public keys. let tpks: Arc>>> = Default::default(); + // Initialize the list of deployment payers. + let deployment_payers: Arc>>> = Default::default(); // Separate the transactions into deploys and executions. let (deployments, executions): (Vec<&Transaction>, Vec<&Transaction>) = @@ -853,6 +878,7 @@ impl> VM { &input_ids.lock(), &output_ids.lock(), &tpks.lock(), + &deployment_payers.lock(), ) { // Continue to the next transaction. return Either::Right((*transaction, reason.to_string())); @@ -870,6 +896,10 @@ impl> VM { output_ids.lock().extend(transaction.output_ids()); // Add the transition public keys to the set of produced transition public keys. tpks.lock().extend(transaction.transition_public_keys()); + // Add the deployment payer to the set of deployment payers. + if let Transaction::Deploy(_, _, _, fee) = transaction { + fee.payer().map(|payer| deployment_payers.lock().insert(payer)); + } Either::Left(*transaction) } // If the transaction is invalid, add it to the list of aborted transactions. From e030982533119826d7abfd2ad33c54464ed06967 Mon Sep 17 00:00:00 2001 From: raychu86 <14917648+raychu86@users.noreply.github.com> Date: Mon, 8 Apr 2024 11:37:12 -0400 Subject: [PATCH 04/11] Add test for multiple dpeloyments for same payer --- ledger/src/tests.rs | 81 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 3 deletions(-) diff --git a/ledger/src/tests.rs b/ledger/src/tests.rs index 5f8f3182c0..7182a9a132 100644 --- a/ledger/src/tests.rs +++ b/ledger/src/tests.rs @@ -1256,6 +1256,63 @@ function simple_output: assert_eq!(block.aborted_transaction_ids(), &vec![transaction_3_id]); } +#[test] +fn test_abort_multiple_deployments_with_same_payer() { + let rng = &mut TestRng::default(); + + // Initialize the test environment. + let crate::test_helpers::TestEnv { ledger, private_key, .. } = crate::test_helpers::sample_test_env(rng); + + // Create two distinct programs + let program_1 = Program::::from_str( + " +program dummy_program_1.aleo; + +function empty_function: + ", + ) + .unwrap(); + + let program_2 = Program::::from_str( + " +program dummy_program_2.aleo; + +function empty_function: + ", + ) + .unwrap(); + + // Create a deployment transaction for the first program with the same public payer. + let deployment_1 = ledger.vm.deploy(&private_key, &program_1, None, 0, None, rng).unwrap(); + let deployment_1_id = deployment_1.id(); + assert!(ledger.check_transaction_basic(&deployment_1, None, rng).is_ok()); + + // Create a deployment transaction for the second program with the same public payer. + let deployment_2 = ledger.vm.deploy(&private_key, &program_2, None, 0, None, rng).unwrap(); + let deployment_2_id = deployment_2.id(); + assert!(ledger.check_transaction_basic(&deployment_2, None, rng).is_ok()); + + // Create a block. + let block = ledger + .prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![deployment_1, deployment_2], rng) + .unwrap(); + + // Check that the next block is valid. + ledger.check_next_block(&block, rng).unwrap(); + + // Add the block to the ledger. + ledger.advance_to_next_block(&block).unwrap(); + + // Enforce that the block transactions were correct. + assert_eq!(block.transactions().num_accepted(), 1); + assert_eq!(block.aborted_transaction_ids(), &vec![deployment_2_id]); + + // Enforce that the first program was deployed and the second was aborted. + assert_eq!(ledger.get_program(*program_1.id()).unwrap(), program_1); + assert!(ledger.vm.transaction_store().contains_transaction_id(&deployment_1_id).unwrap()); + assert!(ledger.vm.block_store().contains_rejected_or_aborted_transaction_id(&deployment_2_id).unwrap()); +} + #[test] fn test_abort_fee_transaction() { let rng = &mut TestRng::default(); @@ -1360,7 +1417,25 @@ fn test_deployment_duplicate_program_id() { let rng = &mut TestRng::default(); // Initialize the test environment. - let crate::test_helpers::TestEnv { ledger, private_key, .. } = crate::test_helpers::sample_test_env(rng); + let crate::test_helpers::TestEnv { ledger, private_key, view_key, .. } = crate::test_helpers::sample_test_env(rng); + + // A helper function to find records. + let find_records = || { + let microcredits = Identifier::from_str("microcredits").unwrap(); + ledger + .find_records(&view_key, RecordsFilter::SlowUnspent(private_key)) + .unwrap() + .filter(|(_, record)| match record.data().get(µcredits) { + Some(Entry::Private(Plaintext::Literal(Literal::U64(amount), _))) => !amount.is_zero(), + _ => false, + }) + .collect::>() + }; + + // Fetch the unspent records. + let records = find_records(); + let record_1 = records[0].clone(); + let record_2 = records[1].clone(); // Create two programs with a duplicate program ID but different mappings let program_1 = Program::::from_str( @@ -1396,12 +1471,12 @@ finalize foo2: .unwrap(); // Create a deployment transaction for the first program. - let deployment_1 = ledger.vm.deploy(&private_key, &program_1, None, 0, None, rng).unwrap(); + let deployment_1 = ledger.vm.deploy(&private_key, &program_1, Some(record_1), 0, None, rng).unwrap(); let deployment_1_id = deployment_1.id(); assert!(ledger.check_transaction_basic(&deployment_1, None, rng).is_ok()); // Create a deployment transaction for the second program. - let deployment_2 = ledger.vm.deploy(&private_key, &program_2, None, 0, None, rng).unwrap(); + let deployment_2 = ledger.vm.deploy(&private_key, &program_2, Some(record_2), 0, None, rng).unwrap(); let deployment_2_id = deployment_2.id(); assert!(ledger.check_transaction_basic(&deployment_2, None, rng).is_ok()); From 1f7cf54602b94b85e26c7eaad74b02db0aa81ab0 Mon Sep 17 00:00:00 2001 From: raychu86 <14917648+raychu86@users.noreply.github.com> Date: Mon, 8 Apr 2024 12:19:43 -0400 Subject: [PATCH 05/11] Check additional abort conditions prior to verification --- ledger/src/tests.rs | 48 ++++++++++++++++++++- synthesizer/src/vm/finalize.rs | 79 +++++++++++++++++++--------------- 2 files changed, 90 insertions(+), 37 deletions(-) diff --git a/ledger/src/tests.rs b/ledger/src/tests.rs index 7182a9a132..a61422c7b4 100644 --- a/ledger/src/tests.rs +++ b/ledger/src/tests.rs @@ -822,6 +822,12 @@ fn test_execute_duplicate_input_ids() { assert_eq!(block.transactions().transaction_ids().collect::>(), vec![&transfer_1_id]); assert_eq!(block.aborted_transaction_ids(), &vec![transfer_2_id, transfer_3_id]); + // Ensure that verification was not run on aborted transactions. + let partially_verified_transaction = ledger.vm().partially_verified_transactions().read().clone(); + assert!(partially_verified_transaction.contains(&transfer_1_id)); + assert!(!partially_verified_transaction.contains(&transfer_2_id)); + assert!(!partially_verified_transaction.contains(&transfer_3_id)); + // Prepare a transfer that will succeed for the subsequent block. let inputs = [Value::from_str(&format!("{address}")).unwrap(), Value::from_str("1000u64").unwrap()]; let transfer_5 = ledger @@ -845,6 +851,11 @@ fn test_execute_duplicate_input_ids() { assert_eq!(block.transactions().num_accepted(), 1); assert_eq!(block.transactions().transaction_ids().collect::>(), vec![&transfer_5_id]); assert_eq!(block.aborted_transaction_ids(), &vec![transfer_4_id]); + + // Ensure that verification was not run on aborted transactions. + let partially_verified_transaction = ledger.vm().partially_verified_transactions().read().clone(); + assert!(partially_verified_transaction.contains(&transfer_5_id)); + assert!(!partially_verified_transaction.contains(&transfer_4_id)); } #[test] @@ -958,6 +969,11 @@ function create_duplicate_record: assert_eq!(block.transactions().transaction_ids().collect::>(), vec![&transfer_1_id]); assert_eq!(block.aborted_transaction_ids(), &vec![transfer_2_id]); + // Ensure that verification was not run on aborted transactions. + let partially_verified_transaction = ledger.vm().partially_verified_transactions().read().clone(); + assert!(partially_verified_transaction.contains(&transfer_1_id)); + assert!(!partially_verified_transaction.contains(&transfer_2_id)); + // Prepare a transfer that will succeed for the subsequent block. let inputs = [Value::from_str(&format!("{address}")).unwrap(), Value::from_str("1000u64").unwrap()]; let transfer_4 = ledger @@ -981,6 +997,11 @@ function create_duplicate_record: assert_eq!(block.transactions().num_accepted(), 1); assert_eq!(block.transactions().transaction_ids().collect::>(), vec![&transfer_4_id]); assert_eq!(block.aborted_transaction_ids(), &vec![transfer_3_id]); + + // Ensure that verification was not run on aborted transactions. + let partially_verified_transaction = ledger.vm().partially_verified_transactions().read().clone(); + assert!(partially_verified_transaction.contains(&transfer_4_id)); + assert!(!partially_verified_transaction.contains(&transfer_3_id)); } #[test] @@ -1088,6 +1109,11 @@ function empty_function: assert_eq!(block.transactions().transaction_ids().collect::>(), vec![&transaction_1_id]); assert_eq!(block.aborted_transaction_ids(), &vec![transaction_2_id]); + // Ensure that verification was not run on aborted transactions. + let partially_verified_transaction = ledger.vm().partially_verified_transactions().read().clone(); + assert!(partially_verified_transaction.contains(&transaction_1_id)); + assert!(!partially_verified_transaction.contains(&transaction_2_id)); + // Prepare a transfer that will succeed for the subsequent block. let inputs = [Value::from_str(&format!("{address}")).unwrap(), Value::from_str("1000u64").unwrap()]; let transfer_transaction = ledger @@ -1117,6 +1143,11 @@ function empty_function: assert_eq!(block.transactions().num_accepted(), 1); assert_eq!(block.transactions().transaction_ids().collect::>(), vec![&transfer_transaction_id]); assert_eq!(block.aborted_transaction_ids(), &vec![transaction_3_id]); + + // Ensure that verification was not run on aborted transactions. + let partially_verified_transaction = ledger.vm().partially_verified_transactions().read().clone(); + assert!(partially_verified_transaction.contains(&transfer_transaction_id)); + assert!(!partially_verified_transaction.contains(&transaction_3_id)); } #[test] @@ -1225,6 +1256,11 @@ function simple_output: assert_eq!(block.transactions().transaction_ids().collect::>(), vec![&transaction_1_id]); assert_eq!(block.aborted_transaction_ids(), &vec![transaction_2_id]); + // Ensure that verification was not run on aborted transactions. + let partially_verified_transaction = ledger.vm().partially_verified_transactions().read().clone(); + assert!(partially_verified_transaction.contains(&transaction_1_id)); + assert!(!partially_verified_transaction.contains(&transaction_2_id)); + // Prepare a transfer that will succeed for the subsequent block. let inputs = [Value::from_str(&format!("{address}")).unwrap(), Value::from_str("1000u64").unwrap()]; let transfer_transaction = ledger @@ -1254,6 +1290,11 @@ function simple_output: assert_eq!(block.transactions().num_accepted(), 1); assert_eq!(block.transactions().transaction_ids().collect::>(), vec![&transfer_transaction_id]); assert_eq!(block.aborted_transaction_ids(), &vec![transaction_3_id]); + + // Ensure that verification was not run on aborted transactions. + let partially_verified_transaction = ledger.vm().partially_verified_transactions().read().clone(); + assert!(partially_verified_transaction.contains(&transfer_transaction_id)); + assert!(!partially_verified_transaction.contains(&transaction_3_id)); } #[test] @@ -1285,12 +1326,10 @@ function empty_function: // Create a deployment transaction for the first program with the same public payer. let deployment_1 = ledger.vm.deploy(&private_key, &program_1, None, 0, None, rng).unwrap(); let deployment_1_id = deployment_1.id(); - assert!(ledger.check_transaction_basic(&deployment_1, None, rng).is_ok()); // Create a deployment transaction for the second program with the same public payer. let deployment_2 = ledger.vm.deploy(&private_key, &program_2, None, 0, None, rng).unwrap(); let deployment_2_id = deployment_2.id(); - assert!(ledger.check_transaction_basic(&deployment_2, None, rng).is_ok()); // Create a block. let block = ledger @@ -1311,6 +1350,11 @@ function empty_function: assert_eq!(ledger.get_program(*program_1.id()).unwrap(), program_1); assert!(ledger.vm.transaction_store().contains_transaction_id(&deployment_1_id).unwrap()); assert!(ledger.vm.block_store().contains_rejected_or_aborted_transaction_id(&deployment_2_id).unwrap()); + + // Ensure that verification was not run on aborted transactions. + let partially_verified_transaction = ledger.vm().partially_verified_transactions().read().clone(); + assert!(partially_verified_transaction.contains(&deployment_1_id)); + assert!(!partially_verified_transaction.contains(&deployment_2_id)); } #[test] diff --git a/synthesizer/src/vm/finalize.rs b/synthesizer/src/vm/finalize.rs index 1ee6474742..df69d19691 100644 --- a/synthesizer/src/vm/finalize.rs +++ b/synthesizer/src/vm/finalize.rs @@ -834,24 +834,61 @@ impl> VM { transactions: &[&'a Transaction], rng: &mut R, ) -> Result<(Vec<&'a Transaction>, Vec<(&'a Transaction, String)>)> { + // Construct the list of transactions that need to verified. + let mut transactions_to_verify = Vec::with_capacity(transactions.len()); // 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()); // Initialize a list of created transition IDs. - let transition_ids: Arc>> = Default::default(); + let mut transition_ids: IndexSet = Default::default(); // Initialize a list of spent input IDs. - let input_ids: Arc>>> = Default::default(); + let mut input_ids: IndexSet> = Default::default(); // Initialize a list of created output IDs. - let output_ids: Arc>>> = Default::default(); + let mut output_ids: IndexSet> = Default::default(); // Initialize the list of created transition public keys. - let tpks: Arc>>> = Default::default(); + let mut tpks: IndexSet> = Default::default(); // Initialize the list of deployment payers. - let deployment_payers: Arc>>> = Default::default(); + let mut deployment_payers: IndexSet> = Default::default(); + + // Abort the transactions that are have duplicates or are invalid. This will prevent the VM from performing + // verification on transactions that would have been aborted in `VM::atomic_speculate`. + for transaction in transactions.iter() { + // Determine if the transaction should be aborted. + match self.should_abort_transaction( + transaction, + &transition_ids, + &input_ids, + &output_ids, + &tpks, + &deployment_payers, + ) { + // Store the aborted transaction. + Some(reason) => aborted_transactions.push((*transaction, reason.to_string())), + // Track the transaction state. + None => { + // Add the transition IDs to the set of produced transition IDs. + transition_ids.extend(transaction.transition_ids()); + // Add the input IDs to the set of spent input IDs. + input_ids.extend(transaction.input_ids()); + // Add the output IDs to the set of produced output IDs. + output_ids.extend(transaction.output_ids()); + // Add the transition public keys to the set of produced transition public keys. + tpks.extend(transaction.transition_public_keys()); + // Add the deployment payer to the set of deployment payers. + if let Transaction::Deploy(_, _, _, fee) = transaction { + fee.payer().map(|payer| deployment_payers.insert(payer)); + } + + // Add the transaction to the list of transactions to verify. + transactions_to_verify.push(transaction); + } + }; + } // Separate the transactions into deploys and executions. let (deployments, executions): (Vec<&Transaction>, Vec<&Transaction>) = - transactions.iter().partition(|tx| tx.is_deploy()); + transactions_to_verify.into_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); @@ -870,38 +907,10 @@ impl> VM { )); } - // Determine if the transaction should be aborted. This will prevent the VM from performing - // verification on transactions that would have been aborted in `VM::atomic_speculate`. - if let Some(reason) = self.should_abort_transaction( - transaction, - &transition_ids.lock(), - &input_ids.lock(), - &output_ids.lock(), - &tpks.lock(), - &deployment_payers.lock(), - ) { - // Continue to the next transaction. - return Either::Right((*transaction, reason.to_string())); - } - // Verify the transaction. match self.check_transaction(transaction, None, &mut rng) { // If the transaction is valid, add it to the list of valid transactions. - Ok(_) => { - // Add the transition IDs to the set of produced transition IDs. - transition_ids.lock().extend(transaction.transition_ids()); - // Add the input IDs to the set of spent input IDs. - input_ids.lock().extend(transaction.input_ids()); - // Add the output IDs to the set of produced output IDs. - output_ids.lock().extend(transaction.output_ids()); - // Add the transition public keys to the set of produced transition public keys. - tpks.lock().extend(transaction.transition_public_keys()); - // Add the deployment payer to the set of deployment payers. - if let Transaction::Deploy(_, _, _, fee) = transaction { - fee.payer().map(|payer| deployment_payers.lock().insert(payer)); - } - Either::Left(*transaction) - } + Ok(_) => Either::Left(*transaction), // If the transaction is invalid, add it to the list of aborted transactions. Err(e) => Either::Right((*transaction, e.to_string())), } From a9f7ce438c45db2ee5a9c139e35ffd92539f54a5 Mon Sep 17 00:00:00 2001 From: ljedrz Date: Mon, 8 Apr 2024 20:44:36 +0200 Subject: [PATCH 06/11] clippy: fix new lints Signed-off-by: ljedrz --- .../process/src/stack/register_types/initialize.rs | 2 +- utilities/src/bytes.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/synthesizer/process/src/stack/register_types/initialize.rs b/synthesizer/process/src/stack/register_types/initialize.rs index ba04cd388c..f35a89f8e5 100644 --- a/synthesizer/process/src/stack/register_types/initialize.rs +++ b/synthesizer/process/src/stack/register_types/initialize.rs @@ -186,7 +186,7 @@ impl RegisterTypes { for operand in async_.operands() { if let Operand::Register(register) = operand { if let Ok(RegisterType::Future(locator)) = register_types.get_type(stack, register) { - assert!(future_registers.remove(&(register.clone(), locator))); + assert!(future_registers.swap_remove(&(register.clone(), locator))); } } } diff --git a/utilities/src/bytes.rs b/utilities/src/bytes.rs index 82eb310d8a..d49bea08fe 100644 --- a/utilities/src/bytes.rs +++ b/utilities/src/bytes.rs @@ -80,7 +80,7 @@ pub trait FromBytes { } } -pub struct ToBytesSerializer(String, Option, PhantomData); +pub struct ToBytesSerializer(PhantomData); impl ToBytesSerializer { /// Serializes a static-sized object as a byte array (without length encoding). @@ -100,7 +100,7 @@ impl ToBytesSerializer { } } -pub struct FromBytesDeserializer(String, Option, PhantomData); +pub struct FromBytesDeserializer(PhantomData); impl<'de, T: FromBytes> FromBytesDeserializer { /// Deserializes a static-sized byte array (without length encoding). @@ -166,12 +166,12 @@ impl<'de, T: FromBytes> FromBytesDeserializer { } } -pub struct FromBytesVisitor<'a>(&'a mut Vec, SmolStr, Option); +pub struct FromBytesVisitor<'a>(&'a mut Vec, SmolStr); impl<'a> FromBytesVisitor<'a> { /// Initializes a new `FromBytesVisitor` with the given `buffer` and `name`. pub fn new(buffer: &'a mut Vec, name: &str) -> Self { - Self(buffer, SmolStr::new(name), None) + Self(buffer, SmolStr::new(name)) } } From dc9e432ea0d498814fa45ade65af88d4e5c4a5cb Mon Sep 17 00:00:00 2001 From: Raymond Chu <14917648+raychu86@users.noreply.github.com> Date: Fri, 12 Apr 2024 09:18:12 -0700 Subject: [PATCH 07/11] Update synthesizer/src/vm/finalize.rs Co-authored-by: vicsn Signed-off-by: Raymond Chu <14917648+raychu86@users.noreply.github.com> --- synthesizer/src/vm/finalize.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synthesizer/src/vm/finalize.rs b/synthesizer/src/vm/finalize.rs index df69d19691..ff0fb21baa 100644 --- a/synthesizer/src/vm/finalize.rs +++ b/synthesizer/src/vm/finalize.rs @@ -812,7 +812,7 @@ impl> VM { // If the transaction is a deployment, ensure that it is not another deployment in the block from the same public fee payer. if let Transaction::Deploy(_, _, _, fee) = transaction { - // If the deployment spender has already deployed in this block, abort the transaction. + // If any public deployment payer has already deployed in this block, abort the transaction. if let Some(payer) = fee.payer() { if deployment_payers.contains(&payer) { return Some(format!("Another deployment in the block from the same public fee payer {payer}")); From c5a1e8361f4956a6ba3126e25cd47c4a25e4887f Mon Sep 17 00:00:00 2001 From: Raymond Chu <14917648+raychu86@users.noreply.github.com> Date: Fri, 12 Apr 2024 09:18:21 -0700 Subject: [PATCH 08/11] Update synthesizer/src/vm/finalize.rs Co-authored-by: vicsn Signed-off-by: Raymond Chu <14917648+raychu86@users.noreply.github.com> --- synthesizer/src/vm/finalize.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synthesizer/src/vm/finalize.rs b/synthesizer/src/vm/finalize.rs index ff0fb21baa..14313e5e1f 100644 --- a/synthesizer/src/vm/finalize.rs +++ b/synthesizer/src/vm/finalize.rs @@ -435,7 +435,7 @@ impl> VM { output_ids.extend(confirmed_transaction.transaction().output_ids()); // Add the transition public keys to the set of produced transition public keys. tpks.extend(confirmed_transaction.transaction().transition_public_keys()); - // Add the deployment payer to the set of deployment payers. + // Add any public deployment payer to the set of deployment payers. if let Transaction::Deploy(_, _, _, fee) = confirmed_transaction.transaction() { fee.payer().map(|payer| deployment_payers.insert(payer)); } From 8343a95abfdb51e41e0c6627c36db388edab4fd8 Mon Sep 17 00:00:00 2001 From: Raymond Chu <14917648+raychu86@users.noreply.github.com> Date: Fri, 12 Apr 2024 09:18:31 -0700 Subject: [PATCH 09/11] Update synthesizer/src/vm/finalize.rs Co-authored-by: vicsn Signed-off-by: Raymond Chu <14917648+raychu86@users.noreply.github.com> --- synthesizer/src/vm/finalize.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synthesizer/src/vm/finalize.rs b/synthesizer/src/vm/finalize.rs index 14313e5e1f..9cfeebc5c4 100644 --- a/synthesizer/src/vm/finalize.rs +++ b/synthesizer/src/vm/finalize.rs @@ -875,7 +875,7 @@ impl> VM { output_ids.extend(transaction.output_ids()); // Add the transition public keys to the set of produced transition public keys. tpks.extend(transaction.transition_public_keys()); - // Add the deployment payer to the set of deployment payers. + // Add any public deployment payer to the set of deployment payers. if let Transaction::Deploy(_, _, _, fee) = transaction { fee.payer().map(|payer| deployment_payers.insert(payer)); } From 69f9100cec622198256c633670f85eb1413590d2 Mon Sep 17 00:00:00 2001 From: raychu86 <14917648+raychu86@users.noreply.github.com> Date: Fri, 12 Apr 2024 18:50:36 -0400 Subject: [PATCH 10/11] Fix test --- ledger/src/tests.rs | 44 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/ledger/src/tests.rs b/ledger/src/tests.rs index 91a8dec5ca..4441210bcf 100644 --- a/ledger/src/tests.rs +++ b/ledger/src/tests.rs @@ -1805,11 +1805,49 @@ fn test_transaction_ordering() { let crate::test_helpers::TestEnv { ledger, private_key, address, .. } = crate::test_helpers::sample_test_env(rng); // Get the public balance of the address. - let public_balance = match ledger.genesis_block.ratifications().iter().next().unwrap() { + let mut public_balance = match ledger.genesis_block.ratifications().iter().next().unwrap() { Ratify::Genesis(_, public_balance, _) => *public_balance.get(&address).unwrap(), _ => panic!("Expected a genesis ratification"), }; + // Sample multiple private keys and addresses. + let private_key_2 = PrivateKey::::new(rng).unwrap(); + let address_2 = Address::try_from(&private_key_2).unwrap(); + let private_key_3 = PrivateKey::::new(rng).unwrap(); + let address_3 = Address::try_from(&private_key_3).unwrap(); + + // Fund a new address. + let amount_1 = 100000000u64; + let inputs = + [Value::from_str(&format!("{address_2}")).unwrap(), Value::from_str(&format!("{amount_1}u64")).unwrap()]; + let transfer_1 = ledger + .vm + .execute(&private_key, ("credits.aleo", "transfer_public"), inputs.iter(), None, 0, None, rng) + .unwrap(); + + let amount_2 = 100000000u64; + let inputs = + [Value::from_str(&format!("{address_3}")).unwrap(), Value::from_str(&format!("{amount_2}u64")).unwrap()]; + let transfer_2 = ledger + .vm + .execute(&private_key, ("credits.aleo", "transfer_public"), inputs.iter(), None, 0, None, rng) + .unwrap(); + + // Update the public balance. + public_balance -= *transfer_1.fee_amount().unwrap(); + public_balance -= *transfer_2.fee_amount().unwrap(); + + // Create a block. + let block = ledger + .prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transfer_1, transfer_2], rng) + .unwrap(); + + // Check that the next block is valid. + ledger.check_next_block(&block, rng).unwrap(); + + // Add the block to the ledger. + ledger.advance_to_next_block(&block).unwrap(); + // Create multiple dummy programs. let program_1 = Program::::from_str( r" @@ -1859,10 +1897,10 @@ finalize foo: let initial_transfer_id = initial_transfer.id(); // Create a deployment transaction. - let deployment_transaction = ledger.vm.deploy(&private_key, &program_1, None, 0, None, rng).unwrap(); + let deployment_transaction = ledger.vm.deploy(&private_key_2, &program_1, None, 0, None, rng).unwrap(); // Create a deployment transaction. - let deployment_transaction_2 = ledger.vm.deploy(&private_key, &program_2, None, 0, None, rng).unwrap(); + let deployment_transaction_2 = ledger.vm.deploy(&private_key_3, &program_2, None, 0, None, rng).unwrap(); // Create a transfer transaction. let inputs = [Value::from_str(&format!("{address}")).unwrap(), Value::from_str("1000000u64").unwrap()]; From 9a2e6aec623d0abddd5f5819044e930c2def4d2a Mon Sep 17 00:00:00 2001 From: Victor Sint Nicolaas Date: Tue, 16 Apr 2024 15:56:25 +0200 Subject: [PATCH 11/11] Add get_delegators_for_validator --- ledger/src/get.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/ledger/src/get.rs b/ledger/src/get.rs index 22259d2517..a92d52d142 100644 --- a/ledger/src/get.rs +++ b/ledger/src/get.rs @@ -241,6 +241,39 @@ impl> Ledger { pub fn get_batch_certificate(&self, certificate_id: &Field) -> Result>> { self.vm.block_store().get_batch_certificate(certificate_id) } + + /// Returns the delegators for the given validator. + pub fn get_delegators_for_validator(&self, validator: &Address) -> Result>> { + // Construct the credits.aleo program ID. + let credits_program_id = ProgramID::from_str("credits.aleo")?; + // Construct the bonded mapping name. + let bonded_mapping = Identifier::from_str("bonded")?; + // Construct the bonded mapping key name. + let bonded_mapping_key = Identifier::from_str("validator")?; + // Get the credits.aleo bonded mapping. + let bonded = self.vm.finalize_store().get_mapping_confirmed(credits_program_id, bonded_mapping)?; + // Select the delegators for the given validator. + cfg_into_iter!(bonded) + .filter_map(|(bonded_address, bond_state)| { + let Plaintext::Literal(Literal::Address(bonded_address), _) = bonded_address else { + return Some(Err(anyhow!("Invalid delegator in finalize storage."))); + }; + let Value::Plaintext(Plaintext::Struct(bond_state, _)) = bond_state else { + return Some(Err(anyhow!("Invalid bond_state in finalize storage."))); + }; + let Some(mapping_validator) = bond_state.get(&bonded_mapping_key) else { + return Some(Err(anyhow!("Invalid bond_state validator in finalize storage."))); + }; + let Plaintext::Literal(Literal::Address(mapping_validator), _) = mapping_validator else { + return Some(Err(anyhow!("Invalid validator in finalize storage."))); + }; + // Select bonded addresses which: + // 1. are bonded to the right validator. + // 2. are not themselves the validator. + (mapping_validator == validator && bonded_address != *validator).then_some(Ok(bonded_address)) + }) + .collect::>() + } } #[cfg(test)]