Skip to content

Commit f01435a

Browse files
blockifier: move logic inside casm_v2hash_resource_estimate
1 parent a3db1a9 commit f01435a

File tree

4 files changed

+143
-138
lines changed

4 files changed

+143
-138
lines changed

crates/blockifier/src/execution/casm_hash_estimation.rs

Lines changed: 102 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -148,15 +148,14 @@ impl From<(ExecutionResources, HashVersion)> for EstimatedExecutionResources {
148148
/// This provides resource estimates rather than exact values.
149149
// TODO(AvivG): Remove allow once used.
150150
#[allow(unused)]
151-
trait EstimateCasmHashResources {
151+
pub(crate) trait EstimateCasmHashResources {
152152
/// Specifies the hash function variant that the estimate is for.
153153
fn hash_version(&self) -> HashVersion;
154154

155155
/// Estimates the Cairo execution resources used when applying the hash function during CASM
156156
/// hashing.
157157
fn estimated_resources_of_hash_function(
158-
&mut self,
159-
_felt_count: FeltSizeCount,
158+
_felt_size_groups: &FeltSizeCount,
160159
) -> EstimatedExecutionResources;
161160

162161
/// Estimates the Cairo execution resources for `compiled_class_hash` in the
@@ -173,158 +172,142 @@ trait EstimateCasmHashResources {
173172

174173
// TODO(AvivG): Remove allow once used.
175174
#[allow(unused)]
176-
struct CasmV1HashResourceEstimate {}
175+
pub(crate) struct CasmV1HashResourceEstimate {}
177176

178177
impl EstimateCasmHashResources for CasmV1HashResourceEstimate {
179178
fn hash_version(&self) -> HashVersion {
180179
HashVersion::V1
181180
}
182181

183182
fn estimated_resources_of_hash_function(
184-
&mut self,
185-
felt_count: FeltSizeCount,
183+
felt_size_groups: &FeltSizeCount,
186184
) -> EstimatedExecutionResources {
187185
EstimatedExecutionResources::V1Hash {
188186
// TODO(AvivG): Consider inlining `poseidon_hash_many_cost` logic here.
189-
resources: poseidon_hash_many_cost(felt_count.n_felts()),
187+
resources: poseidon_hash_many_cost(felt_size_groups.n_felts()),
190188
}
191189
}
192190
}
193191

194192
// TODO(AvivG): Remove allow once used.
195193
#[allow(unused)]
196-
struct CasmV2HashResourceEstimate {}
194+
pub(crate) struct CasmV2HashResourceEstimate {}
197195

198196
impl EstimateCasmHashResources for CasmV2HashResourceEstimate {
199197
fn hash_version(&self) -> HashVersion {
200198
HashVersion::V2
201199
}
202200

201+
/// Estimates resource usage for `encode_felt252_data_and_calc_blake_hash` in the Starknet OS.
202+
///
203+
/// # Encoding Details
204+
/// - Small felts → 2 `u32`s each; Big felts → 8 `u32`s each.
205+
/// - Each felt requires one `range_check` operation.
206+
///
207+
/// # Returns:
208+
/// - `ExecutionResources`: VM resource usage (e.g., n_steps, range checks).
209+
/// - `usize`: number of Blake opcodes used, accounted for separately as those are not reported
210+
/// via `ExecutionResources`.
203211
fn estimated_resources_of_hash_function(
204-
&mut self,
205-
_felt_count: FeltSizeCount,
212+
felt_size_groups: &FeltSizeCount,
206213
) -> EstimatedExecutionResources {
207-
// TODO(AvivG): Use `cost_of_encode_felt252_data_and_calc_blake_hash` once it returns ER.
208-
EstimatedExecutionResources::new(HashVersion::V2)
214+
let n_steps =
215+
Self::estimate_steps_of_encode_felt252_data_and_calc_blake_hash(felt_size_groups);
216+
let builtin_instance_counter = match felt_size_groups.n_felts() {
217+
// The empty case does not use builtins at all.
218+
0 => HashMap::new(),
219+
// One `range_check` per input felt to validate its size + Overhead for the non empty
220+
// case.
221+
_ => HashMap::from([(
222+
BuiltinName::range_check,
223+
felt_size_groups.n_felts() + Self::BASE_RANGE_CHECK_NON_EMPTY,
224+
)]),
225+
};
226+
227+
let resources = ExecutionResources { n_steps, n_memory_holes: 0, builtin_instance_counter };
228+
229+
EstimatedExecutionResources::V2Hash {
230+
resources,
231+
blake_count: felt_size_groups.blake_opcode_count(),
232+
}
209233
}
210234
}
211235

212-
// Constants used for estimating the cost of BLAKE hashing inside Starknet OS.
213-
// These values are based on empirical measurement by running
214-
// `encode_felt252_data_and_calc_blake_hash` on various combinations of big and small felts.
215-
mod blake_estimation {
216-
// Per-felt step cost (measured).
217-
pub const STEPS_BIG_FELT: usize = 45;
218-
pub const STEPS_SMALL_FELT: usize = 15;
236+
impl CasmV2HashResourceEstimate {
237+
// Constants used for estimating the VM execution resources of BLAKE hashing in the Starknet OS.
238+
// Values were obtained empirically by running
239+
// `encode_felt252_data_and_calc_blake_hash` on various combinations of large and small felts.
219240

220-
// One-time overhead.
221-
// Overhead when input fills a full Blake message (16 u32s).
241+
// Per-felt contribution.
242+
pub const STEPS_PER_LARGE_FELT: usize = 45;
243+
pub const STEPS_PER_SMALL_FELT: usize = 15;
244+
245+
// One-time overheads for `encode_felt252_data_and_calc_blake_hash` execution.
246+
// Applied when the input fills an exact Blake message (16-u32).
222247
pub const BASE_STEPS_FULL_MSG: usize = 217;
223-
// Overhead when input results in a partial message (remainder < 16 u32s).
248+
// Applied when the input leaves a remainder (< 16 u32s).
224249
pub const BASE_STEPS_PARTIAL_MSG: usize = 195;
225-
// Extra steps per 2-u32 remainder in partial messages.
250+
// Extra steps added per 2-u32 remainder in partial messages.
226251
pub const STEPS_PER_2_U32_REMINDER: usize = 3;
227-
// Overhead when input for `encode_felt252_data_and_calc_blake_hash` is non-empty.
252+
// Additional `range_check` instances required when the input is non-empty.
228253
pub const BASE_RANGE_CHECK_NON_EMPTY: usize = 3;
229-
// Empty input steps.
230-
pub const STEPS_EMPTY_INPUT: usize = 170;
231-
}
232254

233-
fn base_steps_for_blake_hash(n_u32s: usize) -> usize {
234-
let rem_u32s = n_u32s % FeltSizeCount::U32_WORDS_PER_MESSAGE;
235-
if rem_u32s == 0 {
236-
blake_estimation::BASE_STEPS_FULL_MSG
237-
} else {
238-
// This computation is based on running blake2s with different inputs.
239-
// Note: all inputs expand to an even number of u32s --> `rem_u32s` is always even.
240-
blake_estimation::BASE_STEPS_PARTIAL_MSG
241-
+ (rem_u32s / 2) * blake_estimation::STEPS_PER_2_U32_REMINDER
242-
}
243-
}
255+
// Applied when the input is completely empty.
256+
pub const STEPS_EMPTY_INPUT: usize = 170;
244257

245-
fn felts_steps(n_big_felts: usize, n_small_felts: usize) -> usize {
246-
let big_steps = n_big_felts
247-
.checked_mul(blake_estimation::STEPS_BIG_FELT)
248-
.expect("Overflow computing big felt steps");
249-
let small_steps = n_small_felts
250-
.checked_mul(blake_estimation::STEPS_SMALL_FELT)
251-
.expect("Overflow computing small felt steps");
252-
big_steps.checked_add(small_steps).expect("Overflow computing total felt steps")
253-
}
258+
/// Estimates the total number of VM steps needed to hash the given felts with Blake in the
259+
/// Starknet OS.
260+
fn estimate_steps_of_encode_felt252_data_and_calc_blake_hash(
261+
felt_size_groups: &FeltSizeCount,
262+
) -> usize {
263+
let encoded_u32_len = felt_size_groups.encoded_u32_len();
264+
if encoded_u32_len == 0 {
265+
// The empty input case is a special case.
266+
return Self::STEPS_EMPTY_INPUT;
267+
}
254268

255-
/// Estimates the number of VM steps needed to hash the given felts with Blake in Starknet OS.
256-
/// Each small felt unpacks into 2 u32s, and each big felt into 8 u32s.
257-
/// Adds a base cost depending on whether the total fits exactly into full 16-u32 messages.
258-
fn compute_blake_hash_steps(felt_size_groups: &FeltSizeCount) -> usize {
259-
let total_u32s = felt_size_groups.encoded_u32_len();
260-
if total_u32s == 0 {
261-
// The empty input case is a special case.
262-
return blake_estimation::STEPS_EMPTY_INPUT;
269+
// Adds a base cost depending on whether the total fits exactly into full 16-u32 messages.
270+
let base_steps = if encoded_u32_len % FeltSizeCount::U32_WORDS_PER_MESSAGE == 0 {
271+
Self::BASE_STEPS_FULL_MSG
272+
} else {
273+
// This computation is based on running blake2s with different inputs.
274+
// Note: all inputs expand to an even number of u32s --> `rem_u32s` is always even.
275+
Self::BASE_STEPS_PARTIAL_MSG
276+
+ (encoded_u32_len % FeltSizeCount::U32_WORDS_PER_MESSAGE / 2)
277+
* Self::STEPS_PER_2_U32_REMINDER
278+
};
279+
280+
base_steps
281+
+ felt_size_groups.large * Self::STEPS_PER_LARGE_FELT
282+
+ felt_size_groups.small * Self::STEPS_PER_SMALL_FELT
263283
}
264284

265-
let base_steps = base_steps_for_blake_hash(total_u32s);
266-
let felt_steps = felts_steps(felt_size_groups.large, felt_size_groups.small);
267-
268-
base_steps.checked_add(felt_steps).expect("Overflow computing total Blake hash steps")
269-
}
270-
271-
/// Estimates resource usage for `encode_felt252_data_and_calc_blake_hash` in the Starknet OS.
272-
///
273-
/// # Encoding Details
274-
/// - Small felts → 2 `u32`s each; Big felts → 8 `u32`s each.
275-
/// - Each felt requires one `range_check` operation.
276-
///
277-
/// # Returns:
278-
/// - `ExecutionResources`: VM resource usage (e.g., n_steps, range checks).
279-
/// - `usize`: number of Blake opcodes used, accounted for separately as those are not reported via
280-
/// `ExecutionResources`.
281-
pub fn encode_and_blake_hash_resources(
282-
felt_size_groups: &FeltSizeCount,
283-
) -> EstimatedExecutionResources {
284-
let n_steps = compute_blake_hash_steps(felt_size_groups);
285-
let builtin_instance_counter = match felt_size_groups.n_felts() {
286-
// The empty case does not use builtins at all.
287-
0 => HashMap::new(),
288-
// One `range_check` per input felt to validate its size + Overhead for the non empty case.
289-
_ => HashMap::from([(
290-
BuiltinName::range_check,
291-
felt_size_groups.n_felts() + blake_estimation::BASE_RANGE_CHECK_NON_EMPTY,
292-
)]),
293-
};
294-
295-
let resources = ExecutionResources { n_steps, n_memory_holes: 0, builtin_instance_counter };
296-
297-
EstimatedExecutionResources::V2Hash {
298-
resources,
299-
blake_count: felt_size_groups.blake_opcode_count(),
285+
/// Converts the execution resources and blake opcode count to L2 gas.
286+
///
287+
/// Used for both Stwo ("proving_gas") and Stone ("sierra_gas") estimations, which differ in
288+
/// builtin costs. This unified logic is valid because only the `range_check` builtin is used,
289+
/// and its cost is identical across provers (see `bouncer.get_tx_weights`).
290+
// TODO(AvivG): Move inside blake estimation struct.
291+
pub(crate) fn blake_execution_resources_estimation_to_gas(
292+
resources: EstimatedExecutionResources,
293+
versioned_constants: &VersionedConstants,
294+
blake_opcode_gas: usize,
295+
) -> GasAmount {
296+
// TODO(AvivG): Remove this once gas computation is separated from resource estimation.
297+
assert!(
298+
resources
299+
.resources()
300+
.builtin_instance_counter
301+
.keys()
302+
.all(|&k| k == BuiltinName::range_check),
303+
"Expected either empty builtins or only `range_check` builtin, got: {:?}. This breaks \
304+
the assumption that builtin costs are identical between provers.",
305+
resources.resources().builtin_instance_counter.keys().collect::<Vec<_>>()
306+
);
307+
308+
resources.to_sierra_gas(
309+
|resources| vm_resources_to_sierra_gas(resources, versioned_constants),
310+
Some(blake_opcode_gas),
311+
)
300312
}
301313
}
302-
303-
/// Converts the execution resources and blake opcode count to L2 gas.
304-
///
305-
/// Used for both Stwo ("proving_gas") and Stone ("sierra_gas") estimations, which differ in
306-
/// builtin costs. This unified logic is valid because only the `range_check` builtin is used,
307-
/// and its cost is identical across provers (see `bouncer.get_tx_weights`).
308-
// TODO(AvivG): Move inside blake estimation struct.
309-
pub fn blake_execution_resources_estimation_to_gas(
310-
resources: EstimatedExecutionResources,
311-
versioned_constants: &VersionedConstants,
312-
blake_opcode_gas: usize,
313-
) -> GasAmount {
314-
// TODO(AvivG): Remove this once gas computation is separated from resource estimation.
315-
assert!(
316-
resources
317-
.resources()
318-
.builtin_instance_counter
319-
.keys()
320-
.all(|&k| k == BuiltinName::range_check),
321-
"Expected either empty builtins or only `range_check` builtin, got: {:?}. This breaks the \
322-
assumption that builtin costs are identical between provers.",
323-
resources.resources().builtin_instance_counter.keys().collect::<Vec<_>>()
324-
);
325-
326-
resources.to_sierra_gas(
327-
|resources| vm_resources_to_sierra_gas(resources, versioned_constants),
328-
Some(blake_opcode_gas),
329-
)
330-
}

crates/blockifier/src/execution/casm_hash_estimation_test.rs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@ use pretty_assertions::assert_eq;
77
use rstest::rstest;
88
use starknet_types_core::felt::Felt;
99

10-
use crate::execution::casm_hash_estimation::blake_estimation::STEPS_EMPTY_INPUT;
1110
use crate::execution::casm_hash_estimation::{
12-
compute_blake_hash_steps,
13-
encode_and_blake_hash_resources,
11+
CasmV2HashResourceEstimate,
12+
EstimateCasmHashResources,
1413
EstimatedExecutionResources,
1514
};
1615
use crate::execution::contract_class::FeltSizeCount;
@@ -122,16 +121,30 @@ fn test_u32_constants() {
122121
#[test]
123122
fn test_zero_inputs() {
124123
// logic was written.
125-
let steps = compute_blake_hash_steps(&FeltSizeCount { large: 0, small: 0 });
126-
assert_eq!(steps, STEPS_EMPTY_INPUT, "Unexpected base step cost for zero inputs");
124+
let steps =
125+
CasmV2HashResourceEstimate::estimate_steps_of_encode_felt252_data_and_calc_blake_hash(
126+
&FeltSizeCount { large: 0, small: 0 },
127+
);
128+
assert_eq!(
129+
steps,
130+
CasmV2HashResourceEstimate::STEPS_EMPTY_INPUT,
131+
"Unexpected base step cost for zero inputs"
132+
);
127133

128134
// No opcodes should be emitted.
129135
let opcodes = FeltSizeCount { large: 0, small: 0 }.blake_opcode_count();
130136
assert_eq!(opcodes, 0, "Expected zero BLAKE opcodes for zero inputs");
131137

132138
// Should result in base cost only (no opcode cost).
133-
let resources = encode_and_blake_hash_resources(&FeltSizeCount { large: 0, small: 0 });
134-
let expected = ExecutionResources { n_steps: STEPS_EMPTY_INPUT, ..Default::default() };
139+
let resources =
140+
CasmV2HashResourceEstimate::estimated_resources_of_hash_function(&FeltSizeCount {
141+
large: 0,
142+
small: 0,
143+
});
144+
let expected = ExecutionResources {
145+
n_steps: CasmV2HashResourceEstimate::STEPS_EMPTY_INPUT,
146+
..Default::default()
147+
};
135148
assert_eq!(resources.resources(), &expected, "Unexpected resources values for zero-input hash");
136149
assert_eq!(resources.blake_count(), 0, "Expected zero BLAKE opcodes for zero inputs");
137150
}

crates/blockifier/src/execution/contract_class.rs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ use crate::blockifier_versioned_constants::VersionedConstants;
4343
use crate::bouncer::vm_resources_to_sierra_gas;
4444
use crate::execution::call_info::BuiltinCounterMap;
4545
use crate::execution::casm_hash_estimation::{
46-
blake_execution_resources_estimation_to_gas,
47-
encode_and_blake_hash_resources,
46+
CasmV2HashResourceEstimate,
47+
EstimateCasmHashResources,
4848
};
4949
use crate::execution::entry_point::{EntryPointExecutionContext, EntryPointTypeAndSelector};
5050
use crate::execution::errors::PreExecutionError;
@@ -592,10 +592,11 @@ fn leaf_cost(
592592
blake_opcode_gas: usize,
593593
) -> GasAmount {
594594
// All `len` inputs treated as “big” felts; no small-felt optimization here.
595-
// TODO(AvivG): Call `encode_and_blake_hash_resources` directly, and perform the resource→gas
596-
// conversion only after `estimate_casm_blake_hash_computation_resources` executes.
597-
blake_execution_resources_estimation_to_gas(
598-
encode_and_blake_hash_resources(felt_size_groups),
595+
// TODO(AvivG): Call `estimated_resources_of_hash_function` directly, and perform the
596+
// resource→gas conversion only after `estimate_casm_blake_hash_computation_resources`
597+
// executes.
598+
CasmV2HashResourceEstimate::blake_execution_resources_estimation_to_gas(
599+
CasmV2HashResourceEstimate::estimated_resources_of_hash_function(felt_size_groups),
599600
versioned_constants,
600601
blake_opcode_gas,
601602
)
@@ -630,10 +631,14 @@ fn node_cost(
630631

631632
// Node‐level hash over (hash1, len1, hash2, len2, …): one segment hash (“big” felt))
632633
// and one segment length (“small” felt) per segment.
633-
// TODO(AvivG): Call `encode_and_blake_hash_resources` directly, and perform the resource→gas
634-
// conversion only after `estimate_casm_blake_hash_computation_resources` executes.
635-
let node_hash_cost = blake_execution_resources_estimation_to_gas(
636-
encode_and_blake_hash_resources(&FeltSizeCount { large: segs.len(), small: segs.len() }),
634+
// TODO(AvivG): Call `estimated_resources_of_hash_function` directly, and perform the
635+
// resource→gas conversion only after `estimate_casm_blake_hash_computation_resources`
636+
// executes.
637+
let node_hash_cost = CasmV2HashResourceEstimate::blake_execution_resources_estimation_to_gas(
638+
CasmV2HashResourceEstimate::estimated_resources_of_hash_function(&FeltSizeCount {
639+
large: segs.len(),
640+
small: segs.len(),
641+
}),
637642
versioned_constants,
638643
blake_opcode_gas,
639644
);

crates/starknet_os/src/hints/hint_implementation/blake2s/blake2s_test.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
use std::collections::HashMap;
22

33
use blake2s::encode_felt252_data_and_calc_blake_hash;
4-
use blockifier::execution::casm_hash_estimation::encode_and_blake_hash_resources;
4+
use blockifier::execution::casm_hash_estimation::{
5+
CasmV2HashResourceEstimate,
6+
EstimateCasmHashResources,
7+
};
58
use blockifier::execution::contract_class::FeltSizeCount;
69
use cairo_vm::types::builtin_name::BuiltinName;
710
use cairo_vm::types::layout_name::LayoutName;
@@ -39,7 +42,8 @@ fn data_to_felt_count(data: &[Felt]) -> FeltSizeCount {
3942
/// Return the estimated execution resources for Blake2s hashing.
4043
fn estimated_encode_and_blake_hash_execution_resources(data: &[Felt]) -> ExecutionResources {
4144
let felt_size_groups = data_to_felt_count(data);
42-
let estimated = encode_and_blake_hash_resources(&felt_size_groups);
45+
let estimated =
46+
CasmV2HashResourceEstimate::estimated_resources_of_hash_function(&felt_size_groups);
4347

4448
let mut resources = estimated.resources().clone();
4549
resources.n_steps -= 1;

0 commit comments

Comments
 (0)