1- use std:: collections:: HashSet ;
1+ use std:: collections:: { HashMap , HashSet } ;
2+ use std:: sync:: LazyLock ;
23
34use blockifier:: blockifier_versioned_constants:: {
45 RawVersionedConstants ,
6+ ResourcesParams ,
7+ VariableCallDataFactor ,
58 VariableResourceParams ,
69 VersionedConstants ,
710} ;
@@ -36,10 +39,9 @@ use crate::test_manager::{TestBuilder, TestBuilderConfig, FUNDED_ACCOUNT_ADDRESS
3639use crate :: tests:: NON_TRIVIAL_RESOURCE_BOUNDS ;
3740
3841// TODO(Dori): Delete this, or at least reduce it to a minimal set of unmeasurable syscalls.
39- const UNMEASURABLE_SYSCALLS : [ Selector ; 34 ] = [
42+ const UNMEASURABLE_SYSCALLS : [ Selector ; 33 ] = [
4043 Selector :: DelegateCall ,
4144 Selector :: DelegateL1Handler ,
42- Selector :: Deploy ,
4345 Selector :: EmitEvent ,
4446 Selector :: GetBlockHash ,
4547 Selector :: GetBlockNumber ,
@@ -73,6 +75,24 @@ const UNMEASURABLE_SYSCALLS: [Selector; 34] = [
7375 Selector :: StorageWrite ,
7476] ;
7577
78+ /// Store a mapping from a linearly-charged syscall, with the number of "linear elements" in it's
79+ /// first measurement. For example, if we measure the base and linear costs of a [Selector::Deploy]
80+ /// by running:
81+ /// ```
82+ /// deploy_syscall(stable_class_hash, 3, array![2, 0, 0].span(), true).unwrap_syscall();
83+ /// deploy_syscall(stable_class_hash, 3, array![3, 0, 0, 0].span(), true).unwrap_syscall();
84+ /// ```
85+ /// ... then the linear factor is exactly the difference between the two measurements, because the
86+ /// second measurement has one more linear element (4) than the first measurement (3). However, the
87+ /// first call has 3 elements in it's calldata, so to compute the estimated cost of a zero-element
88+ /// call (base) we need to subtract three times the linear factor from the first measurement. In
89+ /// this case, the value in the mapping will be 3.
90+ /// The second call will always have one more linear element than the first call.
91+ /// Note: Keccak does not store the linear factor in the same entry in the versioned constants, but
92+ /// it does have a measurable linear factor stored under [Selector::KeccakRound].
93+ static SYSCALLS_WITH_LINEAR_FACTOR : LazyLock < HashMap < Selector , usize > > =
94+ LazyLock :: new ( || HashMap :: from ( [ ( Selector :: Deploy , 1 ) , ( Selector :: MetaTxV0 , 1 ) ] ) ) ;
95+
7696/// Measure the OS overhead for each syscall, and compare the results with the latest VC.
7797///
7898/// This test relies on the [starknet_os::hint_processor::os_logger::OsLogger] to capture the
@@ -194,48 +214,76 @@ async fn test_os_resources_regression() {
194214 test_output. perform_default_validations ( ) ;
195215
196216 // Extract syscall resources consumed per syscall.
197- let syscall_traces = test_output. runner_output . txs_trace . last ( ) . unwrap ( ) . get_syscalls ( ) ;
198-
199- // Measure each syscall overhead. If the syscall incurs an inner call, subtract the inner call
200- // overhead.
201217 let mut inner_calls_iter = inner_calls. into_iter ( ) ;
202- let mut visited_syscalls = HashSet :: new ( ) ;
203- let measurements : IndexMap < Selector , ExecutionResources > = syscall_traces
218+ let syscall_traces = test_output . runner_output . txs_trace . last ( ) . unwrap ( ) . get_syscalls ( ) ;
219+ let mut syscalls_iter = syscall_traces
204220 . iter ( )
205- . filter_map ( |syscall_trace| {
206- let selector = syscall_trace. get_selector ( ) ;
207- if UNMEASURABLE_SYSCALLS . contains ( & selector) {
208- return None ;
209- }
210-
211- // Ensure we don't visit the same syscall twice.
212- assert ! (
213- !visited_syscalls. contains( & selector) ,
214- "Syscall {selector:?} was visited twice."
215- ) ;
216- visited_syscalls. insert ( selector) ;
217-
218- // If this syscall incurs an inner call, it should be the next inner call in the
219- // iterator. We assume no inner calls have nested inner calls (all inner calls are
220- // leaves).
221- let inner_overhead = if selector. is_calling_syscall ( ) {
221+ . filter ( |syscall_trace| !UNMEASURABLE_SYSCALLS . contains ( & syscall_trace. get_selector ( ) ) ) ;
222+ let mut measurements: IndexMap < Selector , VariableResourceParams > = IndexMap :: new ( ) ;
223+ // If the syscall incurs an inner call, subtract the inner call overhead.
224+ let mut maybe_deduct_inner =
225+ |total : & ExecutionResources , selector : Selector | -> ExecutionResources {
226+ // We assume no inner calls have nested inner calls (all inner calls are leaves).
227+ let to_deduct = if selector. is_calling_syscall ( ) {
222228 // TODO(Dori): Take opcodes (like blake) into account, instead of using the
223229 // vm_resources field.
224230 inner_calls_iter. next ( ) . unwrap ( ) . resources . vm_resources
225231 } else {
226232 ExecutionResources :: default ( )
227233 } ;
234+ ( total - & to_deduct) . filter_unused_builtins ( )
235+ } ;
236+ while let Some ( syscall_trace) = syscalls_iter. next ( ) {
237+ let selector = syscall_trace. get_selector ( ) ;
228238
229- Some ( (
239+ // Ensure we don't visit the same syscall more than once.
240+ assert ! (
241+ measurements. get( & selector) . is_none( ) ,
242+ "Syscall {selector:?} was visited again, unexpectedly."
243+ ) ;
244+
245+ // If this syscall incurs an inner call, it should be the next inner call in the
246+ // iterator.
247+ let resources = maybe_deduct_inner ( syscall_trace. get_resources ( ) . unwrap ( ) , selector) ;
248+
249+ // If this if a syscall with a linear factor, the next syscall should be an invocation of
250+ // the same syscall with +1 linear element.
251+ let syscall_cost = if let Some ( linear_count_in_base) =
252+ SYSCALLS_WITH_LINEAR_FACTOR . get ( & selector)
253+ {
254+ let next_syscall_trace = syscalls_iter. next ( ) . unwrap ( ) ;
255+ assert_eq ! (
230256 selector,
231- ( syscall_trace. get_resources ( ) . unwrap ( ) - & inner_overhead) . filter_unused_builtins ( ) ,
232- ) )
233- } )
234- . collect ( ) ;
257+ next_syscall_trace. get_selector( ) ,
258+ "Expected next syscall to be the same as the current syscall {selector:?}, but \
259+ got {:?}.",
260+ next_syscall_trace. get_selector( )
261+ ) ;
262+ let next_resources =
263+ maybe_deduct_inner ( next_syscall_trace. get_resources ( ) . unwrap ( ) , selector) ;
264+ let linear_factor_resources = ( & next_resources - & resources) . filter_unused_builtins ( ) ;
265+
266+ // Linear factor is computed; deduct the linear overhead from the base cost to get the
267+ // real base cost.
268+ let constant_resources = ( & resources
269+ - & ( & linear_factor_resources * * linear_count_in_base) )
270+ . filter_unused_builtins ( ) ;
271+
272+ VariableResourceParams :: WithFactor ( ResourcesParams {
273+ constant : constant_resources,
274+ // Syscalls with a linear factor have an unscaled linear factor cost.
275+ calldata_factor : VariableCallDataFactor :: Unscaled ( linear_factor_resources) ,
276+ } )
277+ } else {
278+ VariableResourceParams :: Constant ( resources)
279+ } ;
280+
281+ measurements. insert ( selector, syscall_cost) ;
282+ }
235283
236284 // Make sure we covered all syscalls we expect to.
237285 assert_eq ! (
238- visited_syscalls ,
286+ HashSet :: from_iter ( measurements . keys ( ) . cloned ( ) ) ,
239287 Selector :: iter( )
240288 . collect:: <HashSet <_>>( )
241289 . difference( & UNMEASURABLE_SYSCALLS . iter( ) . cloned( ) . collect:: <HashSet <_>>( ) )
@@ -248,10 +296,7 @@ async fn test_os_resources_regression() {
248296 let mut raw_vc: RawVersionedConstants =
249297 serde_json:: from_str ( VersionedConstants :: json_str ( & version) . unwrap ( ) ) . unwrap ( ) ;
250298 for ( syscall, resources) in measurements {
251- raw_vc
252- . os_resources
253- . execute_syscalls
254- . insert ( syscall, VariableResourceParams :: Constant ( resources) ) ;
299+ raw_vc. os_resources . execute_syscalls . insert ( syscall, resources) ;
255300 }
256301 expect_file ! [ VersionedConstants :: json_path( & version) . unwrap( ) ]
257302 . assert_eq ( & raw_vc. to_string_pretty ( ) ) ;
0 commit comments