From 4a473c5d3fb0a4a441dafecb851bb84be944faa5 Mon Sep 17 00:00:00 2001 From: raychu86 <14917648+raychu86@users.noreply.github.com> Date: Sat, 20 Jan 2024 11:59:09 -0800 Subject: [PATCH 1/3] Abort transactions that attempt to create a duplicate transition_id --- synthesizer/src/vm/finalize.rs | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/synthesizer/src/vm/finalize.rs b/synthesizer/src/vm/finalize.rs index 700a95dbec..212b1a4fb6 100644 --- a/synthesizer/src/vm/finalize.rs +++ b/synthesizer/src/vm/finalize.rs @@ -223,8 +223,10 @@ impl> VM { let mut counter = 0u32; // Initialize a list of spent input IDs. let mut input_ids: IndexSet> = IndexSet::new(); - // Initialize a list of spent output IDs. + // Initialize a list of created output IDs. let mut output_ids: IndexSet> = IndexSet::new(); + // Initialize a list of created transition IDs. + let mut transition_ids: IndexSet = IndexSet::new(); // Finalize the transactions. 'outer: for transaction in transactions { @@ -252,7 +254,7 @@ impl> VM { // Ensure that the transaction is not producing a duplicate output. for output_id in transaction.output_ids() { - // If the output ID is already spent in this block or previous blocks, abort the transaction. + // 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) { @@ -263,6 +265,19 @@ impl> VM { } } + // 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; + } + } + // Process the transaction in an isolated atomic batch. // - If the transaction succeeds, the finalize operations are stored. // - If the transaction fails, the atomic batch is aborted and no finalize operations are stored. @@ -378,8 +393,10 @@ impl> VM { Ok(confirmed_transaction) => { // Add the input IDs to the set of spent input IDs. input_ids.extend(confirmed_transaction.transaction().input_ids()); - // Add the output IDs to the set of spent output IDs. + // Add the output IDs to the set of produced output IDs. output_ids.extend(confirmed_transaction.transaction().output_ids()); + // Add the transition IDs to the set of produced transition IDs. + transition_ids.extend(confirmed_transaction.transaction().transition_ids()); // Store the confirmed transaction. confirmed.push(confirmed_transaction); // Increment the transaction index counter. From 10dfb7261f800ab0a1105d7a041766f72eff147f Mon Sep 17 00:00:00 2001 From: raychu86 <14917648+raychu86@users.noreply.github.com> Date: Sat, 20 Jan 2024 12:00:06 -0800 Subject: [PATCH 2/3] Add test for duplicate transition ids --- ledger/src/tests.rs | 136 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/ledger/src/tests.rs b/ledger/src/tests.rs index 3c5777aadd..b8ff45e39e 100644 --- a/ledger/src/tests.rs +++ b/ledger/src/tests.rs @@ -895,6 +895,142 @@ function create_duplicate_record: assert_eq!(block.aborted_transaction_ids(), &vec![transfer_3_id]); } +#[test] +fn test_execute_duplicate_transition_ids() { + let rng = &mut TestRng::default(); + + // Initialize the test environment. + let crate::test_helpers::TestEnv { ledger, private_key, address, .. } = crate::test_helpers::sample_test_env(rng); + + // Deploy a test program to the ledger. + let program = Program::::from_str( + " +program dummy_program.aleo; + +function empty_function: + ", + ) + .unwrap(); + + // Deploy. + let deployment_transaction = ledger.vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); + // Verify. + ledger.vm().check_transaction(&deployment_transaction, None, rng).unwrap(); + + // Construct the next block. + let block = ledger + .prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![deployment_transaction], 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 a transaction with different transaction ids, but with a fixed transition id. + let mut create_transaction_with_duplicate_transition_id = || -> Transaction { + // Use a fixed seed RNG. + let fixed_rng = &mut TestRng::from_seed(1); + + // Create a transaction with a fixed rng. + let inputs: [Value<_>; 0] = []; + let transaction = ledger + .vm + .execute( + &private_key, + ("dummy_program.aleo", "empty_function"), + inputs.into_iter(), + None, + 0, + None, + fixed_rng, + ) + .unwrap(); + // Extract the execution. + let execution = transaction.execution().unwrap().clone(); + + // Create a new fee for the execution. + let fee_authorization = ledger + .vm + .authorize_fee_public( + &private_key, + *transaction.fee_amount().unwrap(), + 0, + execution.to_execution_id().unwrap(), + rng, + ) + .unwrap(); + let fee = ledger.vm.execute_fee_authorization(fee_authorization, None, rng).unwrap(); + + Transaction::from_execution(execution, Some(fee)).unwrap() + }; + + // Create the first transaction. + let transaction_1 = create_transaction_with_duplicate_transition_id(); + let transaction_1_id = transaction_1.id(); + + // Create a second transaction with the same transition id. + let transaction_2 = create_transaction_with_duplicate_transition_id(); + let transaction_2_id = transaction_2.id(); + + // Create a third transaction with the same transition_id + let transaction_3 = create_transaction_with_duplicate_transition_id(); + let transaction_3_id = transaction_3.id(); + + // Ensure that each transaction has a duplicate transition id. + let tx_1_transition_id = transaction_1.transition_ids().next().unwrap(); + let tx_2_transition_id = transaction_2.transition_ids().next().unwrap(); + let tx_3_transition_id = transaction_3.transition_ids().next().unwrap(); + assert_eq!(tx_1_transition_id, tx_2_transition_id); + assert_eq!(tx_1_transition_id, tx_3_transition_id); + + // Create a block. + let block = ledger + .prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction_1, transaction_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.transactions().transaction_ids().collect::>(), vec![&transaction_1_id]); + assert_eq!(block.aborted_transaction_ids(), &vec![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 + .vm + .execute(&private_key, ("credits.aleo", "transfer_public"), inputs.into_iter(), None, 0, None, rng) + .unwrap(); + let transfer_transaction_id = transfer_transaction.id(); + + // Create a block. + let block = ledger + .prepare_advance_to_next_beacon_block( + &private_key, + vec![], + vec![], + vec![transaction_3, transfer_transaction], + 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.transactions().transaction_ids().collect::>(), vec![&transfer_transaction_id]); + assert_eq!(block.aborted_transaction_ids(), &vec![transaction_3_id]); +} + #[test] fn test_deployment_duplicate_program_id() { let rng = &mut TestRng::default(); From abc20538a560a6d164930c2879550305ab8cdc93 Mon Sep 17 00:00:00 2001 From: raychu86 <14917648+raychu86@users.noreply.github.com> Date: Sat, 20 Jan 2024 12:08:19 -0800 Subject: [PATCH 3/3] Reorder checks --- synthesizer/src/vm/finalize.rs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/synthesizer/src/vm/finalize.rs b/synthesizer/src/vm/finalize.rs index 212b1a4fb6..152ceceb5d 100644 --- a/synthesizer/src/vm/finalize.rs +++ b/synthesizer/src/vm/finalize.rs @@ -221,12 +221,12 @@ impl> VM { let mut deployments = IndexSet::new(); // Initialize a counter for the confirmed transaction index. let mut counter = 0u32; + // Initialize a list of created transition IDs. + let mut transition_ids: IndexSet = IndexSet::new(); // Initialize a list of spent input IDs. let mut input_ids: IndexSet> = IndexSet::new(); // Initialize a list of created output IDs. let mut output_ids: IndexSet> = IndexSet::new(); - // Initialize a list of created transition IDs. - let mut transition_ids: IndexSet = IndexSet::new(); // Finalize the transactions. 'outer: for transaction in transactions { @@ -239,6 +239,19 @@ 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. @@ -265,19 +278,6 @@ impl> VM { } } - // 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; - } - } - // Process the transaction in an isolated atomic batch. // - If the transaction succeeds, the finalize operations are stored. // - If the transaction fails, the atomic batch is aborted and no finalize operations are stored. @@ -391,12 +391,12 @@ impl> VM { match outcome { // If the transaction succeeded, store it and continue to the next transaction. Ok(confirmed_transaction) => { + // Add the transition IDs to the set of produced transition IDs. + transition_ids.extend(confirmed_transaction.transaction().transition_ids()); // Add the input IDs to the set of spent input IDs. input_ids.extend(confirmed_transaction.transaction().input_ids()); // Add the output IDs to the set of produced output IDs. output_ids.extend(confirmed_transaction.transaction().output_ids()); - // Add the transition IDs to the set of produced transition IDs. - transition_ids.extend(confirmed_transaction.transaction().transition_ids()); // Store the confirmed transaction. confirmed.push(confirmed_transaction); // Increment the transaction index counter.