From fc732036eeea975a0c28ff38005842101ea922d3 Mon Sep 17 00:00:00 2001 From: Victor Sint Nicolaas Date: Wed, 6 Mar 2024 00:22:31 +0100 Subject: [PATCH 1/5] Limit program size and add unit test --- console/network/src/lib.rs | 11 +++ synthesizer/program/src/lib.rs | 9 +++ synthesizer/program/src/parse.rs | 112 +++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+) diff --git a/console/network/src/lib.rs b/console/network/src/lib.rs index 45b109c29c..df25eda0d1 100644 --- a/console/network/src/lib.rs +++ b/console/network/src/lib.rs @@ -144,10 +144,21 @@ pub trait Network: /// The maximum number of entries in a record. const MAX_RECORD_ENTRIES: usize = Self::MIN_RECORD_ENTRIES.saturating_add(Self::MAX_DATA_ENTRIES); + /// The maximum program size in bytes. + const MAX_PROGRAM_BYTE_SIZE: usize = 3_500_000; // 3.5 MB + /// The maximum program size in characters. + const MAX_PROGRAM_CHAR_SIZE: usize = 7_000_000; // 7 MB + /// The maximum number of mappings in a program. const MAX_MAPPINGS: usize = 31; /// The maximum number of functions in a program. const MAX_FUNCTIONS: usize = 31; + /// The maximum number of structs in a program. + const MAX_STRUCTS: usize = 10 * Self::MAX_FUNCTIONS; + /// The maximum number of records in a program. + const MAX_RECORDS: usize = 10 * Self::MAX_FUNCTIONS; + /// The maximum number of closures in a program. + const MAX_CLOSURES: usize = 2 * Self::MAX_FUNCTIONS; /// The maximum number of operands in an instruction. const MAX_OPERANDS: usize = Self::MAX_INPUTS; /// The maximum number of instructions in a closure or function. diff --git a/synthesizer/program/src/lib.rs b/synthesizer/program/src/lib.rs index 9ff50db651..6058ffa3ca 100644 --- a/synthesizer/program/src/lib.rs +++ b/synthesizer/program/src/lib.rs @@ -362,6 +362,9 @@ impl, Command: CommandTrait> Pro // Retrieve the struct name. let struct_name = *struct_.name(); + // Ensure the program has not exceeded the maximum number of structs. + ensure!(self.structs.len() < N::MAX_STRUCTS, "Program exceeds the maximum number of structs."); + // Ensure the struct name is new. ensure!(self.is_unique_name(&struct_name), "'{struct_name}' is already in use."); // Ensure the struct name is not a reserved opcode. @@ -420,6 +423,9 @@ impl, Command: CommandTrait> Pro // Retrieve the record name. let record_name = *record.name(); + // Ensure the program has not exceeded the maximum number of records. + ensure!(self.records.len() < N::MAX_RECORDS, "Program exceeds the maximum number of records."); + // Ensure the record name is new. ensure!(self.is_unique_name(&record_name), "'{record_name}' is already in use."); // Ensure the record name is not a reserved opcode. @@ -480,6 +486,9 @@ impl, Command: CommandTrait> Pro // Retrieve the closure name. let closure_name = *closure.name(); + // Ensure the program has not exceeded the maximum number of closures. + ensure!(self.closures.len() < N::MAX_CLOSURES, "Program exceeds the maximum number of closures."); + // Ensure the closure name is new. ensure!(self.is_unique_name(&closure_name), "'{closure_name}' is already in use."); // Ensure the closure name is not a reserved opcode. diff --git a/synthesizer/program/src/parse.rs b/synthesizer/program/src/parse.rs index aeb6822dd1..d1089b8212 100644 --- a/synthesizer/program/src/parse.rs +++ b/synthesizer/program/src/parse.rs @@ -106,10 +106,16 @@ impl, Command: CommandTrait> Fro /// Returns a program from a string literal. fn from_str(string: &str) -> Result { + // Ensure the raw program string is less than MAX_PROGRAM_CHAR_SIZE. + ensure!(string.len() <= N::MAX_PROGRAM_CHAR_SIZE, "Program exceeds N::MAX_PROGRAM_CHAR_SIZE."); + match Self::parse(string) { Ok((remainder, object)) => { // Ensure the remainder is empty. ensure!(remainder.is_empty(), "Failed to parse string. Remaining invalid string is: \"{remainder}\""); + // Ensure the parsed program is less than MAX_PROGRAM_BYTE_SIZE. + let program_byte_len = object.to_bytes_le()?.len(); + ensure!(program_byte_len <= N::MAX_PROGRAM_BYTE_SIZE, "Program exceeds N::MAX_PROGRAM_BYTE_SIZE."); // Return the object. Ok(object) } @@ -255,4 +261,110 @@ function compute: Ok(()) } + + #[test] + fn test_program_size() { + let long_name = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + + // Helper function to generate large structs. + let gen_struct_string = |n: usize| -> String { + let mut s = String::with_capacity(CurrentNetwork::MAX_PROGRAM_CHAR_SIZE); + for i in 0..n { + s.push_str(&format!("struct m{}:\n", i)); + for j in 0..CurrentNetwork::MAX_DATA_ENTRIES { + s.push_str(&format!(" {}{} as u128;\n", long_name, j)); + } + } + s + }; + + // Helper function to generate large records. + let gen_record_string = |n: usize| -> String { + let mut s = String::with_capacity(CurrentNetwork::MAX_PROGRAM_CHAR_SIZE); + for i in 0..n { + s.push_str(&format!("record r{}:\n owner as address.private;\n", i)); + for j in 0..CurrentNetwork::MAX_DATA_ENTRIES { + s.push_str(&format!(" {}{} as u128.private;\n", long_name, j)); + } + } + s + }; + + // Helper function to generate large mappings. + let gen_mapping_string = |n: usize| -> String { + let mut s = String::with_capacity(CurrentNetwork::MAX_PROGRAM_CHAR_SIZE); + for i in 0..n { + s.push_str(&format!( + "mapping {}{}:\n key as field.public;\n value as field.public;\n", + long_name, i + )); + } + s + }; + + // Helper function to generate large closures. + let gen_closure_string = |n: usize| -> String { + let mut s = String::with_capacity(CurrentNetwork::MAX_PROGRAM_CHAR_SIZE); + for i in 0..n { + s.push_str(&format!("closure c{}:\n input r0 as u128;\n", i)); + for j in 0..4000 { + s.push_str(&format!(" add r0 r0 into r{};\n", j)); + } + s.push_str(&format!(" output r{} as u128;\n", 4000)); + } + s + }; + + // Helper function to generate large functions. + let gen_function_string = |n: usize| -> String { + let mut s = String::with_capacity(CurrentNetwork::MAX_PROGRAM_CHAR_SIZE); + for i in 0..n { + s.push_str(&format!("function f{}:\n add 1u128 1u128 into r0;\n", i)); + for j in 0..4000 { + s.push_str(&format!(" add r0 r0 into r{j};\n")); + } + } + s + }; + + // Helper function to generate and parse a program. + let test_parse = |program: String, should_succeed: bool| { + let program = format!("program to_parse.aleo; {}", program); + let result = Program::::from_str(&program); + assert_eq!(result.is_ok(), should_succeed); + }; + + // A program with MAX_STRUCTS should succeed. + test_parse(gen_struct_string(CurrentNetwork::MAX_STRUCTS), true); + // A program with more than MAX_STRUCTS should fail. + test_parse(gen_struct_string(CurrentNetwork::MAX_STRUCTS + 1), false); + // A program with MAX_RECORDS should succeed. + test_parse(gen_record_string(CurrentNetwork::MAX_RECORDS), true); + // A program with more than MAX_RECORDS should fail. + test_parse(gen_record_string(CurrentNetwork::MAX_RECORDS + 1), false); + // A program with MAX_MAPPINGS should succeed. + test_parse(gen_mapping_string(CurrentNetwork::MAX_MAPPINGS), true); + // A program with more than MAX_MAPPINGS should fail. + test_parse(gen_mapping_string(CurrentNetwork::MAX_MAPPINGS + 1), false); + // A program with MAX_CLOSURES should succeed. + test_parse(gen_closure_string(CurrentNetwork::MAX_CLOSURES), true); + // A program with more than MAX_CLOSURES should fail. + test_parse(gen_closure_string(CurrentNetwork::MAX_CLOSURES + 1), false); + // A program with MAX_FUNCTIONS should succeed. + test_parse(gen_function_string(CurrentNetwork::MAX_FUNCTIONS), true); + // A program with more than MAX_FUNCTIONS should fail. + test_parse(gen_function_string(CurrentNetwork::MAX_FUNCTIONS + 1), false); + + // Initialize a program which is too big. + let program_too_big = format!( + "{} {} {} {} {}", + gen_struct_string(CurrentNetwork::MAX_STRUCTS), + gen_record_string(CurrentNetwork::MAX_RECORDS), + gen_mapping_string(CurrentNetwork::MAX_MAPPINGS), + gen_closure_string(CurrentNetwork::MAX_CLOSURES), + gen_function_string(CurrentNetwork::MAX_FUNCTIONS) + ); + // A program which is too big should fail. + test_parse(program_too_big, false); + } } From 9e0f251600525ccbf0e95bef229f8b25d0e99789 Mon Sep 17 00:00:00 2001 From: Victor Sint Nicolaas Date: Wed, 6 Mar 2024 11:49:40 +0100 Subject: [PATCH 2/5] Only set a max on program string length --- console/network/src/lib.rs | 6 ++---- synthesizer/program/src/parse.rs | 17 +++++++---------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/console/network/src/lib.rs b/console/network/src/lib.rs index df25eda0d1..32b376fd5b 100644 --- a/console/network/src/lib.rs +++ b/console/network/src/lib.rs @@ -144,10 +144,8 @@ pub trait Network: /// The maximum number of entries in a record. const MAX_RECORD_ENTRIES: usize = Self::MIN_RECORD_ENTRIES.saturating_add(Self::MAX_DATA_ENTRIES); - /// The maximum program size in bytes. - const MAX_PROGRAM_BYTE_SIZE: usize = 3_500_000; // 3.5 MB - /// The maximum program size in characters. - const MAX_PROGRAM_CHAR_SIZE: usize = 7_000_000; // 7 MB + /// The maximum program size by number of characters. + const MAX_PROGRAM_SIZE: usize = 7_000_000; // 7 MB /// The maximum number of mappings in a program. const MAX_MAPPINGS: usize = 31; diff --git a/synthesizer/program/src/parse.rs b/synthesizer/program/src/parse.rs index d1089b8212..62c50c6317 100644 --- a/synthesizer/program/src/parse.rs +++ b/synthesizer/program/src/parse.rs @@ -106,16 +106,13 @@ impl, Command: CommandTrait> Fro /// Returns a program from a string literal. fn from_str(string: &str) -> Result { - // Ensure the raw program string is less than MAX_PROGRAM_CHAR_SIZE. - ensure!(string.len() <= N::MAX_PROGRAM_CHAR_SIZE, "Program exceeds N::MAX_PROGRAM_CHAR_SIZE."); + // Ensure the raw program string is less than MAX_PROGRAM_SIZE. + ensure!(string.len() <= N::MAX_PROGRAM_SIZE, "Program length exceeds N::MAX_PROGRAM_SIZE."); match Self::parse(string) { Ok((remainder, object)) => { // Ensure the remainder is empty. ensure!(remainder.is_empty(), "Failed to parse string. Remaining invalid string is: \"{remainder}\""); - // Ensure the parsed program is less than MAX_PROGRAM_BYTE_SIZE. - let program_byte_len = object.to_bytes_le()?.len(); - ensure!(program_byte_len <= N::MAX_PROGRAM_BYTE_SIZE, "Program exceeds N::MAX_PROGRAM_BYTE_SIZE."); // Return the object. Ok(object) } @@ -268,7 +265,7 @@ function compute: // Helper function to generate large structs. let gen_struct_string = |n: usize| -> String { - let mut s = String::with_capacity(CurrentNetwork::MAX_PROGRAM_CHAR_SIZE); + let mut s = String::with_capacity(CurrentNetwork::MAX_PROGRAM_SIZE); for i in 0..n { s.push_str(&format!("struct m{}:\n", i)); for j in 0..CurrentNetwork::MAX_DATA_ENTRIES { @@ -280,7 +277,7 @@ function compute: // Helper function to generate large records. let gen_record_string = |n: usize| -> String { - let mut s = String::with_capacity(CurrentNetwork::MAX_PROGRAM_CHAR_SIZE); + let mut s = String::with_capacity(CurrentNetwork::MAX_PROGRAM_SIZE); for i in 0..n { s.push_str(&format!("record r{}:\n owner as address.private;\n", i)); for j in 0..CurrentNetwork::MAX_DATA_ENTRIES { @@ -292,7 +289,7 @@ function compute: // Helper function to generate large mappings. let gen_mapping_string = |n: usize| -> String { - let mut s = String::with_capacity(CurrentNetwork::MAX_PROGRAM_CHAR_SIZE); + let mut s = String::with_capacity(CurrentNetwork::MAX_PROGRAM_SIZE); for i in 0..n { s.push_str(&format!( "mapping {}{}:\n key as field.public;\n value as field.public;\n", @@ -304,7 +301,7 @@ function compute: // Helper function to generate large closures. let gen_closure_string = |n: usize| -> String { - let mut s = String::with_capacity(CurrentNetwork::MAX_PROGRAM_CHAR_SIZE); + let mut s = String::with_capacity(CurrentNetwork::MAX_PROGRAM_SIZE); for i in 0..n { s.push_str(&format!("closure c{}:\n input r0 as u128;\n", i)); for j in 0..4000 { @@ -317,7 +314,7 @@ function compute: // Helper function to generate large functions. let gen_function_string = |n: usize| -> String { - let mut s = String::with_capacity(CurrentNetwork::MAX_PROGRAM_CHAR_SIZE); + let mut s = String::with_capacity(CurrentNetwork::MAX_PROGRAM_SIZE); for i in 0..n { s.push_str(&format!("function f{}:\n add 1u128 1u128 into r0;\n", i)); for j in 0..4000 { From 81e51a8b0c3720173824474505adcf4f05180e6d Mon Sep 17 00:00:00 2001 From: Victor Sint Nicolaas Date: Wed, 6 Mar 2024 11:49:57 +0100 Subject: [PATCH 3/5] Add test to limit number of imports --- synthesizer/program/src/parse.rs | 39 +++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/synthesizer/program/src/parse.rs b/synthesizer/program/src/parse.rs index 62c50c6317..13e58736bf 100644 --- a/synthesizer/program/src/parse.rs +++ b/synthesizer/program/src/parse.rs @@ -263,6 +263,15 @@ function compute: fn test_program_size() { let long_name = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + // Helper function to generate imports. + let gen_import_string = |n: usize| -> String { + let mut s = String::new(); + for i in 0..n { + s.push_str(&format!("import foo{i}.aleo;\n")); + } + s + }; + // Helper function to generate large structs. let gen_struct_string = |n: usize| -> String { let mut s = String::with_capacity(CurrentNetwork::MAX_PROGRAM_SIZE); @@ -325,32 +334,36 @@ function compute: }; // Helper function to generate and parse a program. - let test_parse = |program: String, should_succeed: bool| { - let program = format!("program to_parse.aleo; {}", program); + let test_parse = |imports: &str, body: &str, should_succeed: bool| { + let program = format!("{imports}\nprogram to_parse.aleo;\n\n{body}"); let result = Program::::from_str(&program); assert_eq!(result.is_ok(), should_succeed); }; + // A program with MAX_IMPORTS should succeed. + test_parse(&gen_import_string(CurrentNetwork::MAX_IMPORTS), &gen_struct_string(1), true); + // A program with more than MAX_IMPORTS should fail. + test_parse(&gen_import_string(CurrentNetwork::MAX_IMPORTS + 1), &gen_struct_string(1), false); // A program with MAX_STRUCTS should succeed. - test_parse(gen_struct_string(CurrentNetwork::MAX_STRUCTS), true); + test_parse("", &gen_struct_string(CurrentNetwork::MAX_STRUCTS), true); // A program with more than MAX_STRUCTS should fail. - test_parse(gen_struct_string(CurrentNetwork::MAX_STRUCTS + 1), false); + test_parse("", &gen_struct_string(CurrentNetwork::MAX_STRUCTS + 1), false); // A program with MAX_RECORDS should succeed. - test_parse(gen_record_string(CurrentNetwork::MAX_RECORDS), true); + test_parse("", &gen_record_string(CurrentNetwork::MAX_RECORDS), true); // A program with more than MAX_RECORDS should fail. - test_parse(gen_record_string(CurrentNetwork::MAX_RECORDS + 1), false); + test_parse("", &gen_record_string(CurrentNetwork::MAX_RECORDS + 1), false); // A program with MAX_MAPPINGS should succeed. - test_parse(gen_mapping_string(CurrentNetwork::MAX_MAPPINGS), true); + test_parse("", &gen_mapping_string(CurrentNetwork::MAX_MAPPINGS), true); // A program with more than MAX_MAPPINGS should fail. - test_parse(gen_mapping_string(CurrentNetwork::MAX_MAPPINGS + 1), false); + test_parse("", &gen_mapping_string(CurrentNetwork::MAX_MAPPINGS + 1), false); // A program with MAX_CLOSURES should succeed. - test_parse(gen_closure_string(CurrentNetwork::MAX_CLOSURES), true); + test_parse("", &gen_closure_string(CurrentNetwork::MAX_CLOSURES), true); // A program with more than MAX_CLOSURES should fail. - test_parse(gen_closure_string(CurrentNetwork::MAX_CLOSURES + 1), false); + test_parse("", &gen_closure_string(CurrentNetwork::MAX_CLOSURES + 1), false); // A program with MAX_FUNCTIONS should succeed. - test_parse(gen_function_string(CurrentNetwork::MAX_FUNCTIONS), true); + test_parse("", &gen_function_string(CurrentNetwork::MAX_FUNCTIONS), true); // A program with more than MAX_FUNCTIONS should fail. - test_parse(gen_function_string(CurrentNetwork::MAX_FUNCTIONS + 1), false); + test_parse("", &gen_function_string(CurrentNetwork::MAX_FUNCTIONS + 1), false); // Initialize a program which is too big. let program_too_big = format!( @@ -362,6 +375,6 @@ function compute: gen_function_string(CurrentNetwork::MAX_FUNCTIONS) ); // A program which is too big should fail. - test_parse(program_too_big, false); + test_parse("", &program_too_big, false); } } From 84a9a4518edefb47f4373a5ac1ae9f175f8c0bb5 Mon Sep 17 00:00:00 2001 From: Victor Sint Nicolaas Date: Wed, 6 Mar 2024 12:34:18 +0100 Subject: [PATCH 4/5] Limit program size to 100KB --- console/network/src/lib.rs | 2 +- synthesizer/program/src/parse.rs | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/console/network/src/lib.rs b/console/network/src/lib.rs index 32b376fd5b..cdd08e7227 100644 --- a/console/network/src/lib.rs +++ b/console/network/src/lib.rs @@ -145,7 +145,7 @@ pub trait Network: const MAX_RECORD_ENTRIES: usize = Self::MIN_RECORD_ENTRIES.saturating_add(Self::MAX_DATA_ENTRIES); /// The maximum program size by number of characters. - const MAX_PROGRAM_SIZE: usize = 7_000_000; // 7 MB + const MAX_PROGRAM_SIZE: usize = 100_000; // 100 KB /// The maximum number of mappings in a program. const MAX_MAPPINGS: usize = 31; diff --git a/synthesizer/program/src/parse.rs b/synthesizer/program/src/parse.rs index 13e58736bf..40929f4341 100644 --- a/synthesizer/program/src/parse.rs +++ b/synthesizer/program/src/parse.rs @@ -261,7 +261,8 @@ function compute: #[test] fn test_program_size() { - let long_name = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + // Define variable name for easy experimentation with program sizes. + let var_name = "a"; // Helper function to generate imports. let gen_import_string = |n: usize| -> String { @@ -277,8 +278,8 @@ function compute: let mut s = String::with_capacity(CurrentNetwork::MAX_PROGRAM_SIZE); for i in 0..n { s.push_str(&format!("struct m{}:\n", i)); - for j in 0..CurrentNetwork::MAX_DATA_ENTRIES { - s.push_str(&format!(" {}{} as u128;\n", long_name, j)); + for j in 0..10 { + s.push_str(&format!(" {}{} as u128;\n", var_name, j)); } } s @@ -289,8 +290,8 @@ function compute: let mut s = String::with_capacity(CurrentNetwork::MAX_PROGRAM_SIZE); for i in 0..n { s.push_str(&format!("record r{}:\n owner as address.private;\n", i)); - for j in 0..CurrentNetwork::MAX_DATA_ENTRIES { - s.push_str(&format!(" {}{} as u128.private;\n", long_name, j)); + for j in 0..10 { + s.push_str(&format!(" {}{} as u128.private;\n", var_name, j)); } } s @@ -302,7 +303,7 @@ function compute: for i in 0..n { s.push_str(&format!( "mapping {}{}:\n key as field.public;\n value as field.public;\n", - long_name, i + var_name, i )); } s @@ -313,7 +314,7 @@ function compute: let mut s = String::with_capacity(CurrentNetwork::MAX_PROGRAM_SIZE); for i in 0..n { s.push_str(&format!("closure c{}:\n input r0 as u128;\n", i)); - for j in 0..4000 { + for j in 0..10 { s.push_str(&format!(" add r0 r0 into r{};\n", j)); } s.push_str(&format!(" output r{} as u128;\n", 4000)); @@ -326,7 +327,7 @@ function compute: let mut s = String::with_capacity(CurrentNetwork::MAX_PROGRAM_SIZE); for i in 0..n { s.push_str(&format!("function f{}:\n add 1u128 1u128 into r0;\n", i)); - for j in 0..4000 { + for j in 0..10 { s.push_str(&format!(" add r0 r0 into r{j};\n")); } } @@ -337,6 +338,9 @@ function compute: let test_parse = |imports: &str, body: &str, should_succeed: bool| { let program = format!("{imports}\nprogram to_parse.aleo;\n\n{body}"); let result = Program::::from_str(&program); + if result.is_ok() != should_succeed { + println!("Program failed to parse: {program}"); + } assert_eq!(result.is_ok(), should_succeed); }; From 4f18a8dcd382b9d1bf3b7d2fa767b54478870183 Mon Sep 17 00:00:00 2001 From: Victor Sint Nicolaas Date: Wed, 6 Mar 2024 13:24:42 +0100 Subject: [PATCH 5/5] Ensure we can test spend limit without exceeding program size --- synthesizer/process/src/tests/test_execute.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/synthesizer/process/src/tests/test_execute.rs b/synthesizer/process/src/tests/test_execute.rs index 676e6d7a65..55b7513ab6 100644 --- a/synthesizer/process/src/tests/test_execute.rs +++ b/synthesizer/process/src/tests/test_execute.rs @@ -2616,10 +2616,14 @@ fn test_max_imports() { #[test] fn test_program_exceeding_transaction_spend_limit() { // Construct a finalize body whose finalize cost is excessively large. - let finalize_body = (0..::MAX_COMMANDS) - .map(|i| format!("hash.bhp256 0field into r{i} as field;")) - .collect::>() - .join("\n"); + let mut finalize_body = r" + cast 0u8 0u8 0u8 0u8 0u8 0u8 0u8 0u8 0u8 0u8 0u8 0u8 0u8 0u8 0u8 0u8 into r0 as [u8; 16u32]; + cast r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 r0 into r1 as [[u8; 16u32]; 16u32]; + cast r1 r1 r1 r1 r1 r1 r1 r1 r1 r1 r1 r1 r1 r1 r1 r1 into r2 as [[[u8; 16u32]; 16u32]; 16u32];" + .to_string(); + (3..500).for_each(|i| { + finalize_body.push_str(&format!("hash.bhp256 r2 into r{i} as field;\n")); + }); // Construct the program. let program = Program::from_str(&format!( r"program test_max_spend_limit.aleo;