Skip to content

Commit 1ae17f6

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

3 files changed

Lines changed: 97 additions & 41 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
+ /os_constants/allowed_virtual_os_program_hashes/2: "0x28619dbc9767792fb536aaba7a2d55f70faadf808c95ec66b956d33fdee1bc0"
22
~ /os_resources/execute_syscalls/CallContract/n_steps: 901
3+
~ /os_resources/execute_syscalls/Deploy/constant/builtin_instance_counter/pedersen_builtin: 8
4+
~ /os_resources/execute_syscalls/Deploy/constant/n_steps: 1183
35
~ /os_resources/execute_syscalls/LibraryCall/n_steps: 874

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: 86 additions & 40 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,22 @@ 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+
/// base 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.
89+
/// Note: Keccak does not store the linear factor in the same entry in the versioned constants, but
90+
/// it does have a measurable linear factor stored under [Selector::KeccakRound].
91+
static SYSCALLS_WITH_LINEAR_FACTOR: LazyLock<HashMap<Selector, usize>> =
92+
LazyLock::new(|| HashMap::from([(Selector::Deploy, 1), (Selector::MetaTxV0, 1)]));
93+
7694
/// Measure the OS overhead for each syscall, and compare the results with the latest VC.
7795
///
7896
/// This test relies on the [starknet_os::hint_processor::os_logger::OsLogger] to capture the
@@ -194,48 +212,79 @@ async fn test_os_resources_regression() {
194212
test_output.perform_default_validations();
195213

196214
// 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.
201215
let mut inner_calls_iter = inner_calls.into_iter();
202-
let mut visited_syscalls = HashSet::new();
203-
let measurements: IndexMap<Selector, ExecutionResources> = syscall_traces
216+
let syscall_traces = test_output.runner_output.txs_trace.last().unwrap().get_syscalls();
217+
let mut syscalls_iter = syscall_traces
204218
.iter()
205-
.filter_map(|syscall_trace| {
206-
let selector = syscall_trace.get_selector();
207-
if UNMEASURABLE_SYSCALLS.contains(&selector) {
208-
return None;
209-
}
219+
.filter(|syscall_trace| !UNMEASURABLE_SYSCALLS.contains(&syscall_trace.get_selector()));
220+
let mut measurements: IndexMap<Selector, VariableResourceParams> = IndexMap::new();
221+
// If the syscall incurs an inner call, subtract the inner call overhead.
222+
let mut fetch_inner_resources = |selector: Selector| -> ExecutionResources {
223+
// We assume no inner calls have nested inner calls (all inner calls are leaves).
224+
if selector.is_calling_syscall() {
225+
// TODO(Dori): Take opcodes (like blake) into account, instead of using the vm_resources
226+
// field.
227+
inner_calls_iter.next().unwrap().resources.vm_resources
228+
} else {
229+
ExecutionResources::default()
230+
}
231+
};
232+
while let Some(syscall_trace) = syscalls_iter.next() {
233+
let selector = syscall_trace.get_selector();
210234

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);
235+
// Ensure we don't visit the same syscall more than once.
236+
assert!(
237+
measurements.get(&selector).is_none(),
238+
"Syscall {selector:?} was visited again, unexpectedly."
239+
);
217240

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() {
222-
// TODO(Dori): Take opcodes (like blake) into account, instead of using the
223-
// vm_resources field.
224-
inner_calls_iter.next().unwrap().resources.vm_resources
225-
} else {
226-
ExecutionResources::default()
227-
};
241+
// If this syscall incurs an inner call, it should be the next inner call in the
242+
// iterator.
243+
let inner_overhead = fetch_inner_resources(selector);
244+
// The resources measured here are one of two types: constant, or base cost of a syscall
245+
// with a linear factor.
246+
let mut resources =
247+
(syscall_trace.get_resources().unwrap() - &inner_overhead).filter_unused_builtins();
228248

229-
Some((
249+
// If this if a syscall with a linear factor, the next syscall should be the linear cost.
250+
// Otherwise, this syscall has a constant cost.
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_inner_overhead = fetch_inner_resources(selector);
263+
let next_resources = (next_syscall_trace.get_resources().unwrap()
264+
- &next_inner_overhead)
265+
.filter_unused_builtins();
266+
let linear_factor_resources = (&next_resources - &resources).filter_unused_builtins();
267+
268+
// Linear factor is computed; deduct the linear overhead from the base cost to get the
269+
// real base cost.
270+
resources = (&resources - &(&linear_factor_resources * *linear_count_in_base))
271+
.filter_unused_builtins();
272+
273+
VariableResourceParams::WithFactor(ResourcesParams {
274+
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)