diff --git a/Cargo.lock b/Cargo.lock index 3697583dfc..ceff8f73c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -857,6 +857,26 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum-iterator" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c280b9e6b3ae19e152d8e31cf47f18389781e119d4013a2a2bb0180e5facc635" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b" +dependencies = [ + "proc-macro2", + "quote 1.0.35", + "syn 2.0.48", +] + [[package]] name = "enum_index" version = "0.2.0" @@ -2912,6 +2932,7 @@ name = "snarkvm-console-program" version = "0.16.19" dependencies = [ "bincode", + "enum-iterator", "enum_index", "enum_index_derive", "indexmap 2.1.0", diff --git a/circuit/program/src/data/plaintext/to_bits.rs b/circuit/program/src/data/plaintext/to_bits.rs index c2bea90341..aafec36c10 100644 --- a/circuit/program/src/data/plaintext/to_bits.rs +++ b/circuit/program/src/data/plaintext/to_bits.rs @@ -23,10 +23,12 @@ impl ToBits for Plaintext { Self::Literal(literal, bits_le) => { // Compute the bits of the literal. let bits = bits_le.get_or_init(|| { - let mut bits_le = vec![Boolean::constant(false), Boolean::constant(false)]; // Variant bit. + let mut bits_le = Vec::new(); + bits_le.extend([Boolean::constant(false), Boolean::constant(false)]); // Variant bit. literal.variant().write_bits_le(&mut bits_le); literal.size_in_bits().write_bits_le(&mut bits_le); literal.write_bits_le(&mut bits_le); + bits_le.shrink_to_fit(); bits_le }); // Extend the vector with the bits of the literal. @@ -35,15 +37,17 @@ impl ToBits for Plaintext { Self::Struct(members, bits_le) => { // Compute the bits of the struct. let bits = bits_le.get_or_init(|| { - let mut bits_le = vec![Boolean::constant(false), Boolean::constant(true)]; // Variant bit. + let mut bits_le = Vec::new(); + bits_le.extend([Boolean::constant(false), Boolean::constant(true)]); // Variant bit. U8::constant(console::U8::new(members.len() as u8)).write_bits_le(&mut bits_le); for (identifier, value) in members { let value_bits = value.to_bits_le(); identifier.size_in_bits().write_bits_le(&mut bits_le); identifier.write_bits_le(&mut bits_le); U16::constant(console::U16::new(value_bits.len() as u16)).write_bits_le(&mut bits_le); - bits_le.extend_from_slice(&value_bits); + bits_le.extend(value_bits); } + bits_le.shrink_to_fit(); bits_le }); // Extend the vector with the bits of the struct. @@ -52,13 +56,15 @@ impl ToBits for Plaintext { Self::Array(elements, bits_le) => { // Compute the bits of the array. let bits = bits_le.get_or_init(|| { - let mut bits_le = vec![Boolean::constant(true), Boolean::constant(false)]; // Variant bit. + let mut bits_le = Vec::new(); + bits_le.extend([Boolean::constant(true), Boolean::constant(false)]); // Variant bit. U32::constant(console::U32::new(elements.len() as u32)).write_bits_le(&mut bits_le); for value in elements { let value_bits = value.to_bits_le(); U16::constant(console::U16::new(value_bits.len() as u16)).write_bits_le(&mut bits_le); bits_le.extend(value_bits); } + bits_le.shrink_to_fit(); bits_le }); // Extend the vector with the bits of the array. @@ -73,10 +79,12 @@ impl ToBits for Plaintext { Self::Literal(literal, bits_be) => { // Compute the bits of the literal. let bits = bits_be.get_or_init(|| { - let mut bits_be = vec![Boolean::constant(false), Boolean::constant(false)]; // Variant bit. + let mut bits_be = Vec::new(); + bits_be.extend([Boolean::constant(false), Boolean::constant(false)]); // Variant bit. literal.variant().write_bits_be(&mut bits_be); literal.size_in_bits().write_bits_be(&mut bits_be); literal.write_bits_be(&mut bits_be); + bits_be.shrink_to_fit(); bits_be }); // Extend the vector with the bits of the literal. @@ -85,15 +93,17 @@ impl ToBits for Plaintext { Self::Struct(members, bits_be) => { // Compute the bits of the struct. let bits = bits_be.get_or_init(|| { - let mut bits_be = vec![Boolean::constant(false), Boolean::constant(true)]; // Variant bit. + let mut bits_be = Vec::new(); + bits_be.extend([Boolean::constant(false), Boolean::constant(true)]); // Variant bit. U8::constant(console::U8::new(members.len() as u8)).write_bits_be(&mut bits_be); for (identifier, value) in members { let value_bits = value.to_bits_be(); identifier.size_in_bits().write_bits_be(&mut bits_be); identifier.write_bits_be(&mut bits_be); U16::constant(console::U16::new(value_bits.len() as u16)).write_bits_be(&mut bits_be); - bits_be.extend_from_slice(&value_bits); + bits_be.extend(value_bits); } + bits_be.shrink_to_fit(); bits_be }); // Extend the vector with the bits of the struct. @@ -102,13 +112,15 @@ impl ToBits for Plaintext { Self::Array(elements, bits_be) => { // Compute the bits of the array. let bits = bits_be.get_or_init(|| { - let mut bits_be = vec![Boolean::constant(true), Boolean::constant(false)]; // Variant bit. + let mut bits_be = Vec::new(); + bits_be.extend([Boolean::constant(true), Boolean::constant(false)]); // Variant bit. U32::constant(console::U32::new(elements.len() as u32)).write_bits_be(&mut bits_be); for value in elements { let value_bits = value.to_bits_be(); U16::constant(console::U16::new(value_bits.len() as u16)).write_bits_be(&mut bits_be); bits_be.extend(value_bits); } + bits_be.shrink_to_fit(); bits_be }); // Extend the vector with the bits of the array. diff --git a/console/program/Cargo.toml b/console/program/Cargo.toml index 23df63249a..8f20c2f8ee 100644 --- a/console/program/Cargo.toml +++ b/console/program/Cargo.toml @@ -40,6 +40,9 @@ version = "0.2" [dependencies.enum_index_derive] version = "0.2" +[dependencies.enum-iterator] +version = "2.1" + [dependencies.indexmap] version = "2.0" diff --git a/console/program/src/data/identifier/parse.rs b/console/program/src/data/identifier/parse.rs index daf02185fb..1ab087141b 100644 --- a/console/program/src/data/identifier/parse.rs +++ b/console/program/src/data/identifier/parse.rs @@ -54,7 +54,7 @@ impl FromStr for Identifier { // Ensure that the identifier is not a literal. ensure!( - crate::LiteralType::from_str(identifier).is_err(), + !enum_iterator::all::().any(|lt| lt.type_name() == identifier), "Identifier '{identifier}' is a reserved literal type" ); diff --git a/console/program/src/data_types/literal_type/mod.rs b/console/program/src/data_types/literal_type/mod.rs index d6ddf2f5a0..4fdcfd0692 100644 --- a/console/program/src/data_types/literal_type/mod.rs +++ b/console/program/src/data_types/literal_type/mod.rs @@ -23,10 +23,11 @@ use snarkvm_console_network::prelude::*; use snarkvm_console_types::{prelude::*, Boolean}; use core::fmt::{self, Debug, Display}; +use enum_iterator::Sequence; use num_derive::FromPrimitive; use num_traits::FromPrimitive; -#[derive(Copy, Clone, PartialEq, Eq, Hash, FromPrimitive)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, FromPrimitive, Sequence)] pub enum LiteralType { /// The Aleo address type. Address, @@ -66,7 +67,7 @@ pub enum LiteralType { impl LiteralType { /// Returns the literal type name. - pub fn type_name(&self) -> &str { + pub const fn type_name(&self) -> &str { match self { Self::Address => "address", Self::Boolean => "boolean", diff --git a/console/program/src/data_types/literal_type/parse.rs b/console/program/src/data_types/literal_type/parse.rs index 806eee0dc2..a414e2c3ac 100644 --- a/console/program/src/data_types/literal_type/parse.rs +++ b/console/program/src/data_types/literal_type/parse.rs @@ -20,23 +20,23 @@ impl Parser for LiteralType { fn parse(string: &str) -> ParserResult { // Parse the type from the string. alt(( - map(tag("address"), |_| Self::Address), - map(tag("boolean"), |_| Self::Boolean), - map(tag("field"), |_| Self::Field), - map(tag("group"), |_| Self::Group), - map(tag("i8"), |_| Self::I8), - map(tag("i16"), |_| Self::I16), - map(tag("i32"), |_| Self::I32), - map(tag("i64"), |_| Self::I64), - map(tag("i128"), |_| Self::I128), - map(tag("u8"), |_| Self::U8), - map(tag("u16"), |_| Self::U16), - map(tag("u32"), |_| Self::U32), - map(tag("u64"), |_| Self::U64), - map(tag("u128"), |_| Self::U128), - map(tag("scalar"), |_| Self::Scalar), - map(tag("signature"), |_| Self::Signature), - map(tag("string"), |_| Self::String), + map(tag(Self::Address.type_name()), |_| Self::Address), + map(tag(Self::Boolean.type_name()), |_| Self::Boolean), + map(tag(Self::Field.type_name()), |_| Self::Field), + map(tag(Self::Group.type_name()), |_| Self::Group), + map(tag(Self::I8.type_name()), |_| Self::I8), + map(tag(Self::I16.type_name()), |_| Self::I16), + map(tag(Self::I32.type_name()), |_| Self::I32), + map(tag(Self::I64.type_name()), |_| Self::I64), + map(tag(Self::I128.type_name()), |_| Self::I128), + map(tag(Self::U8.type_name()), |_| Self::U8), + map(tag(Self::U16.type_name()), |_| Self::U16), + map(tag(Self::U32.type_name()), |_| Self::U32), + map(tag(Self::U64.type_name()), |_| Self::U64), + map(tag(Self::U128.type_name()), |_| Self::U128), + map(tag(Self::Scalar.type_name()), |_| Self::Scalar), + map(tag(Self::Signature.type_name()), |_| Self::Signature), + map(tag(Self::String.type_name()), |_| Self::String), ))(string) } } diff --git a/ledger/Cargo.toml b/ledger/Cargo.toml index e75f892a86..709a32abe0 100644 --- a/ledger/Cargo.toml +++ b/ledger/Cargo.toml @@ -149,6 +149,10 @@ package = "snarkvm-ledger-block" path = "./block" features = [ "test" ] +[dev-dependencies.ledger-test-helpers] +package = "snarkvm-ledger-test-helpers" +path = "./test-helpers" + [dev-dependencies.serde_json] version = "1.0" features = [ "preserve_order" ] diff --git a/ledger/src/tests.rs b/ledger/src/tests.rs index 1dd5e6eadd..ef31b48a05 100644 --- a/ledger/src/tests.rs +++ b/ledger/src/tests.rs @@ -25,7 +25,7 @@ use console::{ program::{Entry, Identifier, Literal, Plaintext, ProgramID, Value}, types::U16, }; -use ledger_block::{ConfirmedTransaction, Ratify, Rejected, Transaction}; +use ledger_block::{ConfirmedTransaction, Execution, Ratify, Rejected, Transaction}; use ledger_committee::{Committee, MIN_VALIDATOR_STAKE}; use ledger_store::{helpers::memory::ConsensusMemory, ConsensusStore}; use synthesizer::{program::Program, vm::VM, Stack}; @@ -749,56 +749,107 @@ fn test_execute_duplicate_input_ids() { // Fetch the unspent records. let records = find_records(); - let record_1 = records[0].clone(); + let record_execution = records[0].clone(); + let record_deployment = records[1].clone(); - // Prepare a transfer that spends the record. + // Prepare a transfer that spends a record. let inputs = [ - Value::Record(record_1.clone()), + Value::Record(record_execution.clone()), Value::from_str(&format!("{address}")).unwrap(), Value::from_str("100u64").unwrap(), ]; - let transfer_1 = ledger - .vm - .execute(&private_key, ("credits.aleo", "transfer_private"), inputs.into_iter(), None, 0, None, rng) - .unwrap(); - let transfer_1_id = transfer_1.id(); - // Prepare a transfer that attempts to spend the same record. - let inputs = [ - Value::Record(record_1.clone()), - Value::from_str(&format!("{address}")).unwrap(), - Value::from_str("1000u64").unwrap(), - ]; - let transfer_2 = ledger - .vm - .execute(&private_key, ("credits.aleo", "transfer_private"), inputs.into_iter(), None, 0, None, rng) + let num_duplicate_deployments = 3; + let mut executions = Vec::with_capacity(num_duplicate_deployments + 1); + let mut execution_ids = Vec::with_capacity(num_duplicate_deployments + 1); + let mut deployments = Vec::with_capacity(num_duplicate_deployments); + let mut deployment_ids = Vec::with_capacity(num_duplicate_deployments); + + // Create Executions and Deployments, spending the same record. + for i in 0..num_duplicate_deployments { + // Execute. + let execution = ledger + .vm + .execute(&private_key, ("credits.aleo", "transfer_private"), inputs.clone().iter(), None, 0, None, rng) + .unwrap(); + execution_ids.push(execution.id()); + executions.push(execution); + // Deploy. + let program_id = ProgramID::::from_str(&format!("dummy_program_{i}.aleo")).unwrap(); + let program = Program::::from_str(&format!( + " +program {program_id}; +function foo: + input r0 as u8.private; + async foo r0 into r1; + output r1 as {program_id}/foo.future; +finalize foo: + input r0 as u8.public; + add r0 r0 into r1;", + )) .unwrap(); - let transfer_2_id = transfer_2.id(); + let deployment = + ledger.vm.deploy(&private_key, &program, Some(record_deployment.clone()), 0, None, rng).unwrap(); + deployment_ids.push(deployment.id()); + deployments.push(deployment); + } - // Prepare a transfer that attempts to spend the same record in the fee. + // Create one more execution which spends the record as a fee. let inputs = [Value::from_str(&format!("{address}")).unwrap(), Value::from_str("100u64").unwrap()]; - let transfer_3 = ledger + let execution = ledger .vm .execute( &private_key, ("credits.aleo", "transfer_public"), - inputs.into_iter(), - Some(record_1.clone()), + inputs.clone().iter(), + Some(record_execution.clone()), 0, None, rng, ) .unwrap(); - let transfer_3_id = transfer_3.id(); - - // Prepare a transfer that attempts to spend the same record for the subsequent block. - let inputs = - [Value::Record(record_1), Value::from_str(&format!("{address}")).unwrap(), Value::from_str("1000u64").unwrap()]; - let transfer_4 = ledger + execution_ids.push(execution.id()); + executions.push(execution); + + // Select a transaction to mutate by a malicious validator. + let transaction_to_mutate = executions.last().unwrap().clone(); + + // Create a mutated execution which adds one transition, resulting in a different transaction id. + // This simulates a malicious validator re-using execution content. + let execution_to_mutate = transaction_to_mutate.execution().unwrap(); + // Sample a transition. + let sample = ledger_test_helpers::sample_transition(rng); + // Extend the transitions. + let mutated_transitions = std::iter::once(sample).chain(execution_to_mutate.transitions().cloned()); + // Create a mutated execution. + let mutated_execution = Execution::from( + mutated_transitions, + execution_to_mutate.global_state_root(), + execution_to_mutate.proof().cloned(), + ) + .unwrap(); + // Create a new fee for the execution. + let fee_authorization = ledger .vm - .execute(&private_key, ("credits.aleo", "transfer_private"), inputs.into_iter(), None, 0, None, rng) + .authorize_fee_public( + &private_key, + *executions.last().unwrap().fee_amount().unwrap(), + 0, + mutated_execution.to_execution_id().unwrap(), + rng, + ) .unwrap(); - let transfer_4_id = transfer_4.id(); + let fee = ledger.vm.execute_fee_authorization(fee_authorization, None, rng).unwrap(); + // Create a mutated transaction. + let mutated_transaction = Transaction::from_execution(mutated_execution, Some(fee)).unwrap(); + execution_ids.push(mutated_transaction.id()); + executions.push(mutated_transaction); + + // Create a mutated execution which just takes the fee transition, resulting in a different transaction id. + // This simulates a malicious validator transforming a transaction to a fee transaction. + let mutated_transaction = Transaction::from_fee(transaction_to_mutate.fee_transition().unwrap()).unwrap(); + execution_ids.push(mutated_transaction.id()); + executions.push(mutated_transaction); // Create a block. let block = ledger @@ -806,7 +857,15 @@ fn test_execute_duplicate_input_ids() { &private_key, vec![], vec![], - vec![transfer_1, transfer_2, transfer_3], + vec![ + executions.pop().unwrap(), + executions.pop().unwrap(), + executions.pop().unwrap(), + executions.pop().unwrap(), + executions.pop().unwrap(), + deployments.pop().unwrap(), + deployments.pop().unwrap(), + ], rng, ) .unwrap(); @@ -818,27 +877,45 @@ fn test_execute_duplicate_input_ids() { 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_1_id]); - assert_eq!(block.aborted_transaction_ids(), &vec![transfer_2_id, transfer_3_id]); - - // Ensure that verification was not run on aborted transactions. + assert_eq!(block.transactions().num_accepted(), 2); + println!("execution_ids: {:?}", execution_ids); + assert_eq!(block.transactions().transaction_ids().collect::>(), vec![&execution_ids[2], &deployment_ids[2]]); + assert_eq!(block.aborted_transaction_ids(), &vec![ + execution_ids[5], + execution_ids[4], + execution_ids[3], + execution_ids[1], + deployment_ids[1] + ]); + + // Ensure that verification was not run on aborted deployments. 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)); + + assert!(partially_verified_transaction.contains(&execution_ids[2])); + assert!(partially_verified_transaction.contains(&deployment_ids[2])); + assert!(!partially_verified_transaction.contains(&execution_ids[1])); + assert!(!partially_verified_transaction.contains(&deployment_ids[1])); + assert!(!partially_verified_transaction.contains(&execution_ids[3])); + assert!(!partially_verified_transaction.contains(&execution_ids[4])); // Verification was run, but the execution was invalid. + assert!(!partially_verified_transaction.contains(&execution_ids[5])); // 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 + let transfer = ledger .vm .execute(&private_key, ("credits.aleo", "transfer_public"), inputs.into_iter(), None, 0, None, rng) .unwrap(); - let transfer_5_id = transfer_5.id(); + let transfer_id = transfer.id(); // Create a block. let block = ledger - .prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transfer_4, transfer_5], rng) + .prepare_advance_to_next_beacon_block( + &private_key, + vec![], + vec![], + vec![executions.pop().unwrap(), deployments.pop().unwrap(), transfer], + rng, + ) .unwrap(); // Check that the next block is valid. @@ -849,13 +926,14 @@ fn test_execute_duplicate_input_ids() { // Enforce that the block transactions were correct. 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]); + assert_eq!(block.transactions().transaction_ids().collect::>(), vec![&transfer_id]); + assert_eq!(block.aborted_transaction_ids(), &vec![execution_ids[0], deployment_ids[0]]); - // Ensure that verification was not run on aborted transactions. + // Ensure that verification was not run on transactions aborted in a previous block. 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)); + assert!(partially_verified_transaction.contains(&transfer_id)); + assert!(!partially_verified_transaction.contains(&execution_ids[0])); + assert!(!partially_verified_transaction.contains(&deployment_ids[0])); } #[test] @@ -863,7 +941,8 @@ fn test_execute_duplicate_output_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); + let crate::test_helpers::TestEnv { ledger, private_key, view_key, address, .. } = + crate::test_helpers::sample_test_env(rng); // Deploy a test program to the ledger. let program = Program::::from_str( @@ -896,8 +975,25 @@ function create_duplicate_record: // Add the block to the ledger. ledger.advance_to_next_block(&block).unwrap(); - // Create a transaction with different transition ids, but with a fixed output record (output ID). - let mut create_transaction_with_duplicate_output_id = |x: u64| -> Transaction { + // 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(); + + // Create an execution with different transition ids, but with a fixed output record (output ID). + let mut create_execution_with_duplicate_output_id = |x: u64| -> Transaction { // Use a fixed seed RNG. let fixed_rng = &mut TestRng::from_seed(1); @@ -934,16 +1030,61 @@ function create_duplicate_record: Transaction::from_execution(execution, Some(fee)).unwrap() }; + // Create an deployment with different transition ids, but with a fixed output record (output ID). + let create_deployment_with_duplicate_output_id = |x: u64| -> Transaction { + // Use a fixed seed RNG. + let fixed_rng = &mut TestRng::from_seed(1); + + // Deploy a test program to the ledger. + let program = Program::::from_str(&format!( + " +program dummy_program_{x}.aleo; + +record dummy_program: + owner as address.private; + rand_var as u64.private; + +function create_duplicate_record: + input r0 as u64.private; + cast self.caller 1u64 into r1 as dummy_program.record; + output r1 as dummy_program.record;" + )) + .unwrap(); + + // Create a transaction with a fixed rng. + let transaction = ledger.vm.deploy(&private_key, &program, None, 0, None, fixed_rng).unwrap(); + + // Extract the deployment and owner. + let deployment = transaction.deployment().unwrap().clone(); + let owner = *transaction.owner().unwrap(); + + // Create a new fee for the execution. + let fee_authorization = ledger + .vm + .authorize_fee_private( + &private_key, + record_1.clone(), + *transaction.fee_amount().unwrap(), + 0, + deployment.to_deployment_id().unwrap(), + fixed_rng, + ) + .unwrap(); + let fee = ledger.vm.execute_fee_authorization(fee_authorization, None, fixed_rng).unwrap(); + + Transaction::from_deployment(owner, deployment, fee).unwrap() + }; + // Create the first transfer. - let transfer_1 = create_transaction_with_duplicate_output_id(1); + let transfer_1 = create_execution_with_duplicate_output_id(1); let transfer_1_id = transfer_1.id(); // Create a second transfer with the same output id. - let transfer_2 = create_transaction_with_duplicate_output_id(2); + let transfer_2 = create_execution_with_duplicate_output_id(2); let transfer_2_id = transfer_2.id(); // Create a third transfer with the same output id. - let transfer_3 = create_transaction_with_duplicate_output_id(3); + let transfer_3 = create_execution_with_duplicate_output_id(3); let transfer_3_id = transfer_3.id(); // Ensure that each transaction has a duplicate output id. @@ -953,9 +1094,34 @@ function create_duplicate_record: assert_eq!(tx_1_output_id, tx_2_output_id); assert_eq!(tx_1_output_id, tx_3_output_id); + // Create the first deployment. + let deployment_1 = create_deployment_with_duplicate_output_id(1); + let deployment_1_id = deployment_1.id(); + + // Create a second deployment with the same output id. + let deployment_2 = create_deployment_with_duplicate_output_id(2); + let deployment_2_id = deployment_2.id(); + + // Create a third deployment with the same output id. + let deployment_3 = create_deployment_with_duplicate_output_id(3); + let deployment_3_id = deployment_3.id(); + + // Ensure that each transaction has a duplicate output id. + let deployment_1_output_id = deployment_1.output_ids().next().unwrap(); + let deployment_2_output_id = deployment_2.output_ids().next().unwrap(); + let deployment_3_output_id = deployment_3.output_ids().next().unwrap(); + assert_eq!(deployment_1_output_id, deployment_2_output_id); + assert_eq!(deployment_1_output_id, deployment_3_output_id); + // Create a block. let block = ledger - .prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transfer_1, transfer_2], rng) + .prepare_advance_to_next_beacon_block( + &private_key, + vec![], + vec![], + vec![transfer_1, transfer_2, deployment_1, deployment_2], + rng, + ) .unwrap(); // Check that the next block is valid. @@ -965,14 +1131,16 @@ function create_duplicate_record: 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_1_id]); - assert_eq!(block.aborted_transaction_ids(), &vec![transfer_2_id]); + assert_eq!(block.transactions().num_accepted(), 2); + assert_eq!(block.transactions().transaction_ids().collect::>(), vec![&transfer_1_id, &deployment_1_id]); + assert_eq!(block.aborted_transaction_ids(), &vec![transfer_2_id, deployment_2_id]); - // Ensure that verification was not run on aborted transactions. + // Ensure that verification was not run on aborted deployments. let partially_verified_transaction = ledger.vm().partially_verified_transactions().read().clone(); assert!(partially_verified_transaction.contains(&transfer_1_id)); + assert!(partially_verified_transaction.contains(&deployment_1_id)); assert!(!partially_verified_transaction.contains(&transfer_2_id)); + assert!(!partially_verified_transaction.contains(&deployment_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()]; @@ -984,7 +1152,13 @@ function create_duplicate_record: // Create a block. let block = ledger - .prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transfer_3, transfer_4], rng) + .prepare_advance_to_next_beacon_block( + &private_key, + vec![], + vec![], + vec![transfer_3, transfer_4, deployment_3], + rng, + ) .unwrap(); // Check that the next block is valid. @@ -996,12 +1170,13 @@ function create_duplicate_record: // Enforce that the block transactions were correct. 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]); + assert_eq!(block.aborted_transaction_ids(), &vec![transfer_3_id, deployment_3_id]); - // Ensure that verification was not run on aborted transactions. + // Ensure that verification was not run on transactions aborted in a previous block. 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)); + assert!(!partially_verified_transaction.contains(&deployment_3_id)); } #[test] @@ -1037,6 +1212,9 @@ function empty_function: ledger.advance_to_next_block(&block).unwrap(); // Create a transaction with different transaction IDs, but with a fixed transition ID. + // NOTE: there's no use creating deployments with duplicate (fee) transition ids, + // as this is only possible if they have duplicate programs, duplicate transaction_ids, + // which will not abort but fail on check_next_block. let mut create_transaction_with_duplicate_transition_id = || -> Transaction { // Use a fixed seed RNG. let fixed_rng = &mut TestRng::from_seed(1); @@ -1144,7 +1322,7 @@ function empty_function: 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. + // Ensure that verification was not run on transactions aborted in a previous block. 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)); @@ -1185,7 +1363,10 @@ function simple_output: // Add the block to the ledger. ledger.advance_to_next_block(&block).unwrap(); - // Create a transaction with different transaction ids, but with a TPK. + // Create a transaction with different transaction ids, but with a duplicate TPK. + // NOTE: there's no use creating deployments with duplicate (fee) TPKs, + // as this is only possible if they have duplicate programs, duplicate transaction_ids, + // which will not abort but fail on check_next_block. let mut create_transaction_with_duplicate_tpk = |function: &str| -> Transaction { // Use a fixed seed RNG. let fixed_rng = &mut TestRng::from_seed(1); @@ -1291,7 +1472,7 @@ function simple_output: 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. + // Ensure that verification was not run on transactions aborted in a previous block. 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)); diff --git a/synthesizer/src/vm/finalize.rs b/synthesizer/src/vm/finalize.rs index a16dbc4af8..bc67e22a8b 100644 --- a/synthesizer/src/vm/finalize.rs +++ b/synthesizer/src/vm/finalize.rs @@ -855,6 +855,12 @@ impl> VM { // 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() { + // Abort the transaction early if it is a fee transaction. + if transaction.is_fee() { + aborted_transactions.push((*transaction, "Fee transactions are not allowed in speculate".to_string())); + continue; + } + // Determine if the transaction should be aborted. match self.should_abort_transaction( transaction, @@ -900,14 +906,6 @@ impl> VM { // Verify the transactions and collect the error message if there is one. let (valid, invalid): (Vec<_>, Vec<_>) = cfg_into_iter!(transactions).zip(rngs).partition_map(|(transaction, mut rng)| { - // Abort the transaction if it is a fee transaction. - if transaction.is_fee() { - return Either::Right(( - *transaction, - "Fee transactions are not allowed in speculate".to_string(), - )); - } - // Verify the transaction. match self.check_transaction(transaction, None, &mut rng) { // If the transaction is valid, add it to the list of valid transactions. diff --git a/utilities/src/parallel.rs b/utilities/src/parallel.rs index be2520f7ef..1a73bfce03 100644 --- a/utilities/src/parallel.rs +++ b/utilities/src/parallel.rs @@ -292,3 +292,19 @@ macro_rules! cfg_sort_by_cached_key { $self.par_sort_by_cached_key($closure); }}; } + +/// Returns a sorted, by-value iterator for the given IndexMap/IndexSet +#[macro_export] +macro_rules! cfg_sorted_by { + ($self: expr, $closure: expr) => {{ + #[cfg(feature = "serial")] + { + $self.sorted_by($closure) + } + + #[cfg(not(feature = "serial"))] + { + $self.par_sorted_by($closure) + } + }}; +}