Skip to content

Commit

Permalink
Merge pull request #2312 from AleoHQ/feat/duplicate-transition-ids
Browse files Browse the repository at this point in the history
Abort transactions with duplicated `TransitionID`s
  • Loading branch information
howardwu authored Jan 20, 2024
2 parents 62a59b9 + abc2053 commit 84c4512
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 3 deletions.
136 changes: 136 additions & 0 deletions ledger/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<CurrentNetwork>::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<CurrentNetwork> {
// 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<_>>(), 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<_>>(), 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();
Expand Down
23 changes: 20 additions & 3 deletions synthesizer/src/vm/finalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,9 +221,11 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
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<N::TransitionID> = IndexSet::new();
// Initialize a list of spent input IDs.
let mut input_ids: IndexSet<Field<N>> = IndexSet::new();
// Initialize a list of spent output IDs.
// Initialize a list of created output IDs.
let mut output_ids: IndexSet<Field<N>> = IndexSet::new();

// Finalize the transactions.
Expand All @@ -237,6 +239,19 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
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.
Expand All @@ -252,7 +267,7 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {

// 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)
{
Expand Down Expand Up @@ -376,9 +391,11 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
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 spent output IDs.
// Add the output IDs to the set of produced output IDs.
output_ids.extend(confirmed_transaction.transaction().output_ids());
// Store the confirmed transaction.
confirmed.push(confirmed_transaction);
Expand Down

0 comments on commit 84c4512

Please sign in to comment.