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,77 @@ 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+ if selector. is_calling_syscall ( ) {
222228 // TODO(Dori): Take opcodes (like blake) into account, instead of using the
223229 // vm_resources field.
224- inner_calls_iter. next ( ) . unwrap ( ) . resources . vm_resources
230+ let to_deduct = inner_calls_iter. next ( ) . unwrap ( ) . resources . vm_resources ;
231+ ( & total - & to_deduct) . filter_unused_builtins ( )
225232 } else {
226- ExecutionResources :: default ( )
227- } ;
233+ total
234+ }
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 =
248+ maybe_deduct_inner ( syscall_trace. get_resources ( ) . unwrap ( ) . clone ( ) , selector) ;
249+
250+ // If this is a syscall with a linear factor, the next syscall should be an invocation of
251+ // the same syscall with +1 linear element.
252+ let syscall_cost = if let Some ( linear_count_in_base) =
253+ SYSCALLS_WITH_LINEAR_FACTOR . get ( & selector)
254+ {
255+ let next_syscall_trace = syscalls_iter. next ( ) . unwrap ( ) ;
256+ assert_eq ! (
230257 selector,
231- ( syscall_trace. get_resources ( ) . unwrap ( ) - & inner_overhead) . filter_unused_builtins ( ) ,
232- ) )
233- } )
234- . collect ( ) ;
258+ next_syscall_trace. get_selector( ) ,
259+ "Expected next syscall to be the same as the current syscall {selector:?}, but \
260+ got {:?}.",
261+ next_syscall_trace. get_selector( )
262+ ) ;
263+ let next_resources =
264+ maybe_deduct_inner ( next_syscall_trace. get_resources ( ) . unwrap ( ) . clone ( ) , selector) ;
265+ let linear_factor_resources = ( & next_resources - & resources) . filter_unused_builtins ( ) ;
266+
267+ // Linear factor is computed; deduct the linear overhead from the base cost to get the
268+ // real base cost.
269+ let constant_resources = ( & resources
270+ - & ( & linear_factor_resources * * linear_count_in_base) )
271+ . filter_unused_builtins ( ) ;
272+
273+ VariableResourceParams :: WithFactor ( ResourcesParams {
274+ constant : constant_resources,
275+ // Syscalls with a linear factor have an unscaled linear factor cost.
276+ calldata_factor : VariableCallDataFactor :: Unscaled ( linear_factor_resources) ,
277+ } )
278+ } else {
279+ VariableResourceParams :: Constant ( resources)
280+ } ;
281+
282+ measurements. insert ( selector, syscall_cost) ;
283+ }
235284
236285 // Make sure we covered all syscalls we expect to.
237286 assert_eq ! (
238- visited_syscalls ,
287+ HashSet :: from_iter ( measurements . keys ( ) . cloned ( ) ) ,
239288 Selector :: iter( )
240289 . collect:: <HashSet <_>>( )
241290 . difference( & UNMEASURABLE_SYSCALLS . iter( ) . cloned( ) . collect:: <HashSet <_>>( ) )
@@ -248,10 +297,7 @@ async fn test_os_resources_regression() {
248297 let mut raw_vc: RawVersionedConstants =
249298 serde_json:: from_str ( VersionedConstants :: json_str ( & version) . unwrap ( ) ) . unwrap ( ) ;
250299 for ( syscall, resources) in measurements {
251- raw_vc
252- . os_resources
253- . execute_syscalls
254- . insert ( syscall, VariableResourceParams :: Constant ( resources) ) ;
300+ raw_vc. os_resources . execute_syscalls . insert ( syscall, resources) ;
255301 }
256302 expect_file ! [ VersionedConstants :: json_path( & version) . unwrap( ) ]
257303 . assert_eq ( & raw_vc. to_string_pretty ( ) ) ;
0 commit comments