diff --git a/crates/blockifier/Cargo.toml b/crates/blockifier/Cargo.toml index 18eb9ade6f7..97170f7b9a6 100644 --- a/crates/blockifier/Cargo.toml +++ b/crates/blockifier/Cargo.toml @@ -56,6 +56,7 @@ derive_more = { workspace = true, features = [ "from", "into_iterator", ] } +expect-test.workspace = true indexmap.workspace = true itertools.workspace = true keccak.workspace = true @@ -84,7 +85,6 @@ validator.workspace = true assert_matches.workspace = true blockifier_test_utils.workspace = true criterion = { workspace = true, features = ["html_reports"] } -expect-test.workspace = true glob.workspace = true hex.workspace = true itertools.workspace = true diff --git a/crates/blockifier/src/execution/casm_hash_estimation.rs b/crates/blockifier/src/execution/casm_hash_estimation.rs index 5a079238b66..2d8f79cb1b8 100644 --- a/crates/blockifier/src/execution/casm_hash_estimation.rs +++ b/crates/blockifier/src/execution/casm_hash_estimation.rs @@ -1,7 +1,9 @@ use std::collections::BTreeMap; +use std::sync::LazyLock; use cairo_vm::types::builtin_name::BuiltinName; use cairo_vm::vm::runners::cairo_runner::ExecutionResources; +use expect_test::{expect, Expect}; use crate::execution::call_info::{ExtendedExecutionResources, OpcodeName}; use crate::execution::contract_class::{ @@ -237,6 +239,36 @@ impl EstimateCasmHashResources for CasmV1HashResourceEstimate { } } +// Base number of VM steps applied when the input to Blake hashing is empty. +// Determined empirically by running `encode_felt252_data_and_calc_blake_hash` on empty input. +pub const STEPS_EMPTY_INPUT_EXPECT: Expect = expect!["169"]; +pub static STEPS_EMPTY_INPUT: LazyLock = + LazyLock::new(|| STEPS_EMPTY_INPUT_EXPECT.data.parse().expect("Expected data is a usize")); + +// The constants used are empirical, based on running `encode_felt252_data_and_calc_blake_hash` +// on combinations of large and small felts. +// VM steps per large felt. +pub const STEPS_PER_LARGE_FELT_EXPECT: Expect = expect!["45"]; +pub static STEPS_PER_LARGE_FELT: LazyLock = + LazyLock::new(|| STEPS_PER_LARGE_FELT_EXPECT.data.parse().expect("Expected data is a usize")); +// VM steps per small felt. +pub const STEPS_PER_SMALL_FELT_EXPECT: Expect = expect!["15"]; +pub static STEPS_PER_SMALL_FELT: LazyLock = + LazyLock::new(|| STEPS_PER_SMALL_FELT_EXPECT.data.parse().expect("Expected data is a usize")); +// Base overhead when input exactly fills a 16-u32 Blake message. +pub const BASE_STEPS_FULL_MSG_EXPECT: Expect = expect!["216"]; +pub static BASE_STEPS_FULL_MSG: LazyLock = + LazyLock::new(|| BASE_STEPS_FULL_MSG_EXPECT.data.parse().expect("Expected data is a usize")); +// Base overhead when the input leaves a remainder (< 16 u32s) for a Blake message. +pub const BASE_STEPS_PARTIAL_MSG_EXPECT: Expect = expect!["194"]; +pub static BASE_STEPS_PARTIAL_MSG: LazyLock = + LazyLock::new(|| BASE_STEPS_PARTIAL_MSG_EXPECT.data.parse().expect("Expected data is a usize")); +// Extra VM steps added per 2-u32 remainder in partial Blake messages. +pub const STEPS_PER_2_U32_REMINDER_EXPECT: Expect = expect!["3"]; +pub static STEPS_PER_2_U32_REMINDER: LazyLock = LazyLock::new(|| { + STEPS_PER_2_U32_REMINDER_EXPECT.data.parse().expect("Expected data is a usize") +}); + pub struct CasmV2HashResourceEstimate {} impl CasmV2HashResourceEstimate { @@ -248,23 +280,6 @@ impl CasmV2HashResourceEstimate { // Input for Blake hash function is a sequence of 16 `u32` words. pub const U32_WORDS_PER_MESSAGE: usize = 16; - // Base number of VM steps applied when the input to Blake hashing is empty. - // Determined empirically by running `encode_felt252_data_and_calc_blake_hash` on empty input. - pub const STEPS_EMPTY_INPUT: usize = 169; - - // The constants used are empirical, based on running `encode_felt252_data_and_calc_blake_hash` - // on combinations of large and small felts. - // VM steps per large felt. - pub const STEPS_PER_LARGE_FELT: usize = 45; - // VM steps per small felt. - pub const STEPS_PER_SMALL_FELT: usize = 15; - // Base overhead when input exactly fills a 16-u32 Blake message. - pub const BASE_STEPS_FULL_MSG: usize = 216; - // Base overhead when the input leaves a remainder (< 16 u32s) for a Blake message. - pub const BASE_STEPS_PARTIAL_MSG: usize = 194; - // Extra VM steps added per 2-u32 remainder in partial Blake messages. - pub const STEPS_PER_2_U32_REMINDER: usize = 3; - /// Estimates the number of VM steps required to hash the given felts with Blake in Starknet OS. /// /// - Each small felt unpacks into 2 `u32`s. @@ -277,23 +292,22 @@ impl CasmV2HashResourceEstimate { let encoded_u32_len = felt_size_groups.encoded_u32_len(); if encoded_u32_len == 0 { // The empty input case is a special case. - return Self::STEPS_EMPTY_INPUT; + return *STEPS_EMPTY_INPUT; } // Adds a base cost depending on whether the total fits exactly into full 16-u32 messages. let base_steps = if encoded_u32_len.is_multiple_of(Self::U32_WORDS_PER_MESSAGE) { - Self::BASE_STEPS_FULL_MSG + *BASE_STEPS_FULL_MSG } else { // This computation is based on running blake2s with different inputs. // Note: all inputs expand to an even number of u32s --> `rem_u32s` is always even. - Self::BASE_STEPS_PARTIAL_MSG - + (encoded_u32_len % Self::U32_WORDS_PER_MESSAGE / 2) - * Self::STEPS_PER_2_U32_REMINDER + *BASE_STEPS_PARTIAL_MSG + + (encoded_u32_len % Self::U32_WORDS_PER_MESSAGE / 2) * *STEPS_PER_2_U32_REMINDER }; base_steps - + felt_size_groups.large * Self::STEPS_PER_LARGE_FELT - + felt_size_groups.small * Self::STEPS_PER_SMALL_FELT + + felt_size_groups.large * *STEPS_PER_LARGE_FELT + + felt_size_groups.small * *STEPS_PER_SMALL_FELT } } diff --git a/crates/blockifier/src/execution/casm_hash_estimation_test.rs b/crates/blockifier/src/execution/casm_hash_estimation_test.rs index 34e76d9ec29..cc489320e6d 100644 --- a/crates/blockifier/src/execution/casm_hash_estimation_test.rs +++ b/crates/blockifier/src/execution/casm_hash_estimation_test.rs @@ -6,6 +6,7 @@ use crate::execution::call_info::OpcodeName; use crate::execution::casm_hash_estimation::{ CasmV2HashResourceEstimate, EstimateCasmHashResources, + STEPS_EMPTY_INPUT, }; use crate::execution::contract_class::FeltSizeCount; @@ -29,11 +30,7 @@ fn test_zero_inputs() { CasmV2HashResourceEstimate::estimate_steps_of_encode_felt252_data_and_calc_blake_hash( &FeltSizeCount::default(), ); - assert_eq!( - steps, - CasmV2HashResourceEstimate::STEPS_EMPTY_INPUT, - "Unexpected base step cost for zero inputs" - ); + assert_eq!(steps, *STEPS_EMPTY_INPUT, "Unexpected base step cost for zero inputs"); // No opcodes should be emitted. let opcodes = FeltSizeCount::default().blake_opcode_count(); @@ -42,10 +39,7 @@ fn test_zero_inputs() { // Should result in base cost only (no opcode cost). let resources = CasmV2HashResourceEstimate::estimated_resources_of_hash_function(&FeltSizeCount::default()); - let expected = ExecutionResources { - n_steps: CasmV2HashResourceEstimate::STEPS_EMPTY_INPUT, - ..Default::default() - }; + let expected = ExecutionResources { n_steps: *STEPS_EMPTY_INPUT, ..Default::default() }; assert_eq!(resources.vm_resources, expected, "Unexpected resources values for zero-input hash"); assert_eq!( *resources.opcode_instance_counter.get(&OpcodeName::blake).unwrap_or(&0), diff --git a/crates/starknet_os/src/hints/hint_implementation/blake2s/blake2s_test.rs b/crates/starknet_os/src/hints/hint_implementation/blake2s/blake2s_test.rs index aaef343e09e..b452a7b4b08 100644 --- a/crates/starknet_os/src/hints/hint_implementation/blake2s/blake2s_test.rs +++ b/crates/starknet_os/src/hints/hint_implementation/blake2s/blake2s_test.rs @@ -3,6 +3,15 @@ use std::collections::HashMap; use blockifier::execution::casm_hash_estimation::{ CasmV2HashResourceEstimate, EstimateCasmHashResources, + BASE_STEPS_FULL_MSG_EXPECT, + BASE_STEPS_PARTIAL_MSG_EXPECT, + STEPS_EMPTY_INPUT_EXPECT, + STEPS_PER_2_U32_REMINDER, + STEPS_PER_2_U32_REMINDER_EXPECT, + STEPS_PER_LARGE_FELT, + STEPS_PER_LARGE_FELT_EXPECT, + STEPS_PER_SMALL_FELT, + STEPS_PER_SMALL_FELT_EXPECT, }; use blockifier::execution::contract_class::FeltSizeCount; use cairo_vm::types::builtin_name::BuiltinName; @@ -84,6 +93,9 @@ fn cairo_encode_felt252_data_and_calc_blake_hash_steps(input: &[Felt]) -> usize } /// Asserts the estimated constants correspond to empiric measurements. +/// +/// To fix the constants, it may be necessary to run with UPDATE_EXPECT=1 more than once. +/// /// Note that the resulting constants cannot be used to compute the precise number of steps required /// for any given input. As an example, the total steps required for input [x; n] where x is a small /// felt and n ranges from 0 to 9 is: [169, 212, 230, 248, 266, 284, 302, 320, 336, 332]. The first @@ -102,7 +114,7 @@ fn test_blake_step_constants() { // Test empty input. let steps_empty = cairo_encode_felt252_data_and_calc_blake_hash_steps(&[]); - assert_eq!(steps_empty, CasmV2HashResourceEstimate::STEPS_EMPTY_INPUT); + STEPS_EMPTY_INPUT_EXPECT.assert_eq(&steps_empty.to_string()); // Start with a baseline of one full word. let one_message_large_felts = vec![large_felt; LARGE_FELTS_PER_MESSAGE]; @@ -118,7 +130,7 @@ fn test_blake_step_constants() { .collect::>(), ) - baseline_steps) / LARGE_FELTS_PER_MESSAGE; - assert_eq!(large_felt_overhead, CasmV2HashResourceEstimate::STEPS_PER_LARGE_FELT); + STEPS_PER_LARGE_FELT_EXPECT.assert_eq(&large_felt_overhead.to_string()); // Add another full word of small felts to compute the overhead per small felt. let one_message_small_felts = vec![small_felt; SMALL_FELTS_PER_MESSAGE]; @@ -130,12 +142,12 @@ fn test_blake_step_constants() { .collect::>(), ) - baseline_steps) / SMALL_FELTS_PER_MESSAGE; - assert_eq!(small_felt_overhead, CasmV2HashResourceEstimate::STEPS_PER_SMALL_FELT); + STEPS_PER_SMALL_FELT_EXPECT.assert_eq(&small_felt_overhead.to_string()); // Compute the full-word overhead by subtracting the overhead of one word of large felts, from // the result of exactly one word of large felts. let full_word_overhead = baseline_steps - (large_felt_overhead * LARGE_FELTS_PER_MESSAGE); - assert_eq!(full_word_overhead, CasmV2HashResourceEstimate::BASE_STEPS_FULL_MSG); + BASE_STEPS_FULL_MSG_EXPECT.assert_eq(&full_word_overhead.to_string()); // Compute the two-word partial overhead by computing: // X, one full message of large felts + one half-message of small felts. @@ -160,18 +172,18 @@ fn test_blake_step_constants() { let remainder = one_plus_half_message_plus_small_felt_steps - one_plus_half_message_steps - small_felt_overhead; - assert_eq!(remainder, CasmV2HashResourceEstimate::STEPS_PER_2_U32_REMINDER); + STEPS_PER_2_U32_REMINDER_EXPECT.assert_eq(&remainder.to_string()); // Reuse the above computation to deduce the constant overhead for the case where the input does // not fit into an exact multiple of messages. let partial_message_overhead = one_plus_half_message_steps // Partial message: small felt overhead. - - (SMALL_FELTS_PER_MESSAGE / 2) * CasmV2HashResourceEstimate::STEPS_PER_SMALL_FELT + - (SMALL_FELTS_PER_MESSAGE / 2) * *STEPS_PER_SMALL_FELT // Partial message: remainder per word-pair in the partial message. - - (SMALL_FELTS_PER_MESSAGE / 2) * CasmV2HashResourceEstimate::STEPS_PER_2_U32_REMINDER + - (SMALL_FELTS_PER_MESSAGE / 2) * *STEPS_PER_2_U32_REMINDER // Overhead of first (full) message: two large felts. - - LARGE_FELTS_PER_MESSAGE * CasmV2HashResourceEstimate::STEPS_PER_LARGE_FELT; - assert_eq!(partial_message_overhead, CasmV2HashResourceEstimate::BASE_STEPS_PARTIAL_MSG); + - LARGE_FELTS_PER_MESSAGE * *STEPS_PER_LARGE_FELT; + BASE_STEPS_PARTIAL_MSG_EXPECT.assert_eq(&partial_message_overhead.to_string()); } /// Test that compares Cairo and Rust implementations of