Skip to content

Commit c34d43e

Browse files
starknet_os: os resources test - measure linear factor, add deploy
1 parent f8ac910 commit c34d43e

2 files changed

Lines changed: 94 additions & 40 deletions

File tree

crates/blockifier_test_utils/resources/feature_contracts/cairo1/os_resources_test_contract.cairo

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#[starknet::contract(account)]
33
mod OsResourcesTestContract {
44
use starknet::info::SyscallResultTrait;
5-
use starknet::syscalls::{call_contract_syscall, library_call_syscall};
5+
use starknet::syscalls::{call_contract_syscall, deploy_syscall, library_call_syscall};
66
use starknet::{ClassHash, ContractAddress};
77

88
const STABLE_EXTERNAL_ENTRY_POINT_SELECTOR: felt252 = selector!("external");
@@ -40,5 +40,13 @@ mod OsResourcesTestContract {
4040
stable_class_hash, STABLE_EXTERNAL_ENTRY_POINT_SELECTOR, array![0].span(),
4141
)
4242
.unwrap_syscall();
43+
44+
// deploy syscall. The resources this syscall consumes can vary depending on the deployed
45+
// contract address, in a non-trivial way (see `normalize_address` in the cairo0 core). For
46+
// this reason we deploy from zero, and choose a specific salt.
47+
// base (no calldata):
48+
deploy_syscall(stable_class_hash, 3, array![0].span(), true).unwrap_syscall();
49+
// linear factor (calldata len = 1):
50+
deploy_syscall(stable_class_hash, 3, array![1, 0].span(), true).unwrap_syscall();
4351
}
4452
}

crates/starknet_os_flow_tests/src/os_resources_test.rs

Lines changed: 85 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
use std::collections::HashSet;
1+
use std::collections::{HashMap, HashSet};
2+
use std::sync::LazyLock;
23

34
use 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
3639
use 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

Comments
 (0)