def test_cairo_pie_memory_invalid_address(cairo_pie: CairoPie): # Write to an invalid address. cairo_pie.memory[RelocatableValue( segment_index=cairo_pie.metadata.ret_pc_segment.index, offset=0)] = 0 with pytest.raises( AssertionError, match='Invalid memory cell address.'): cairo_pie.run_validity_checks()
def test_cairo_pie_memory_negative_address(cairo_pie: CairoPie): # Write to a negative address. cairo_pie.memory.set_without_checks(RelocatableValue( segment_index=cairo_pie.metadata.program_segment.index, offset=-5), 0) with pytest.raises( AssertionError, match='Invalid memory cell address.'): cairo_pie.run_validity_checks()
def test_cairo_pie_memory_invalid_value(cairo_pie: CairoPie): # Write a value after the execution segment. output_end = RelocatableValue( segment_index=cairo_pie.metadata.execution_segment.index, offset=cairo_pie.metadata.execution_segment.size) cairo_pie.memory[output_end] = output_end + 2 # It should fail because the address is outside the segment expected size. with pytest.raises( AssertionError, match='Invalid memory cell address.'): cairo_pie.run_validity_checks() # Increase the size. cairo_pie.metadata.execution_segment.size += 1 # Now it should fail because of the value. with pytest.raises(AssertionError, match='Invalid memory cell value.'): cairo_pie.run_validity_checks()
def add_job(self, cairo_pie: CairoPie) -> str: """ Sends a job to the SHARP. cairo_pie is the product of running the corresponding Cairo program locally (using cairo-run --cairo_pie_output). Returns job_key - a unique id of the job in the SHARP system. """ res = self._send( 'add_job', {'cairo_pie': base64.b64encode(cairo_pie.serialize()).decode('ascii')}) assert 'cairo_job_key' in res, f'Error when sending job to SHARP: {res}.' return res['cairo_job_key']
def test_cairo_pie_serialize_deserialize(): program = compile_cairo( code=[('%builtins output pedersen range_check ecdsa\nmain:\n[ap] = [ap]\n', '')], prime=DEFAULT_PRIME) metadata = CairoPieMetadata( program=program.stripped(), program_segment=SegmentInfo(0, 10), execution_segment=SegmentInfo(1, 20), ret_fp_segment=SegmentInfo(6, 12), ret_pc_segment=SegmentInfo(7, 21), builtin_segments={ 'a': SegmentInfo(4, 15), }, extra_segments=[], ) memory = MemoryDict({ 1: 2, RelocatableValue(3, 4): RelocatableValue(6, 7), }) additional_data = {'c': ['d', 3]} execution_resources = ExecutionResources( n_steps=10, n_memory_holes=7, builtin_instance_counter={ 'output': 6, 'pedersen': 3, } ) cairo_pie = CairoPie( metadata=metadata, memory=memory, additional_data=additional_data, execution_resources=execution_resources ) fileobj = io.BytesIO() cairo_pie.to_file(fileobj) actual_cairo_pie = CairoPie.from_file(fileobj) assert cairo_pie == actual_cairo_pie
def get_cairo_pie(self) -> CairoPie: """ Constructs and returns a CairoPie representing the current VM run. """ builtin_segments = self.get_builtin_segments_info() known_segment_indices = WriteOnceDict() for segment_info in builtin_segments.values(): known_segment_indices[segment_info.index] = None # Note that n_used_builtins might be smaller then len(builtin_segments). n_used_builtins = len(self.program.builtins) ret_fp, ret_pc = (self.vm_memory[self.execution_base + n_used_builtins + i] for i in range(2)) assert isinstance( ret_fp, RelocatableValue), f'Expecting a relocatable value got {ret_fp}.' assert isinstance( ret_pc, RelocatableValue), f'Expecting a relocatable value got {ret_pc}.' assert self.segments.segment_sizes[ret_fp.segment_index] == 0, \ f'Unexpected ret_fp_segment size {self.segments.segment_sizes[ret_fp.segment_index]}' assert self.segments.segment_sizes[ret_pc.segment_index] == 0, \ f'Unexpected ret_pc_segment size {self.segments.segment_sizes[ret_pc.segment_index]}' for addr in self.program_base, self.execution_base, ret_fp, ret_pc: assert addr.offset == 0, 'Expecting a 0 offset.' known_segment_indices[addr.segment_index] = None # Put all the remaining segments in extra_segments. extra_segments = [ SegmentInfo(idx, size) for idx, size in sorted(self.segments.segment_sizes.items()) if idx not in known_segment_indices ] execution_size = self.vm.run_context.ap - self.execution_base cairo_pie_metadata = CairoPieMetadata( program=self.program.stripped(), program_segment=SegmentInfo(index=self.program_base.segment_index, size=len(self.program.data)), execution_segment=SegmentInfo( index=self.execution_base.segment_index, size=execution_size), ret_fp_segment=SegmentInfo(ret_fp.segment_index, 0), ret_pc_segment=SegmentInfo(ret_pc.segment_index, 0), builtin_segments=builtin_segments, extra_segments=extra_segments, ) execution_resources = self.get_execution_resources() return CairoPie( metadata=cairo_pie_metadata, memory=self.vm.run_context.memory, additional_data={ name: builtin.get_additional_data() for name, builtin in self.builtin_runners.items() }, execution_resources=execution_resources, )
def test_cairo_pie_validity_invalid_builtin_list_execution_resources(cairo_pie: CairoPie): cairo_pie.execution_resources.builtin_instance_counter['tmp_builtin'] = \ cairo_pie.execution_resources.builtin_instance_counter['output_builtin'] with pytest.raises( AssertionError, match='Builtin list mismatch in execution_resources.'): cairo_pie.run_validity_checks()
def test_cairo_pie_validity_invalid_builtin_segments(cairo_pie: CairoPie): cairo_pie.metadata.builtin_segments['tmp'] = cairo_pie.metadata.builtin_segments['output'] with pytest.raises( AssertionError, match='Builtin list mismatch in builtin_segments.'): cairo_pie.run_validity_checks()
def test_cairo_pie_validity_invalid_builtin_list(cairo_pie: CairoPie): cairo_pie.program.builtins.append('output') with pytest.raises( AssertionError, match='Invalid builtin list.'): cairo_pie.run_validity_checks()
def test_cairo_pie_validity_invalid_program_size(cairo_pie: CairoPie): cairo_pie.metadata.program_segment.size += 1 with pytest.raises( AssertionError, match='Program length does not match the program segment size.'): cairo_pie.run_validity_checks()
def cairo_run(args): trace_needed = args.tracer or args.profile_output is not None trace_file = args.trace_file if trace_file is None and trace_needed: # If --tracer or --profile_output is used, use a temporary file as trace_file. trace_file = tempfile.NamedTemporaryFile(mode='wb') memory_file = args.memory_file if memory_file is None and trace_needed: # If --tracer or --profile_output is used, use a temporary file as memory_file. memory_file = tempfile.NamedTemporaryFile(mode='wb') debug_info_file = args.debug_info_file if debug_info_file is None and trace_needed: # If --tracer or --profile_output is used, use a temporary file as debug_info_file. debug_info_file = tempfile.NamedTemporaryFile(mode='w') ret_code = 0 cairo_pie_input = None if args.program is not None: program: ProgramBase = load_program(args.program) initial_memory = MemoryDict() steps_input = args.steps else: assert args.run_from_cairo_pie is not None assert args.steps is None and args.min_steps is None, \ '--steps and --min_steps cannot be specified in --run_from_cairo_pie mode.' cairo_pie_input = CairoPie.from_file(args.run_from_cairo_pie) try: cairo_pie_input.run_validity_checks() except Exception as exc: # Trim error message in case it's too long. msg = str(exc)[:10000] raise CairoRunError( f'Security check for the CairoPIE input failed: {msg}') program = cairo_pie_input.program initial_memory = cairo_pie_input.memory steps_input = cairo_pie_input.execution_resources.n_steps runner = CairoRunner(program=program, layout=args.layout, memory=initial_memory, proof_mode=args.proof_mode) runner.initialize_segments() end = runner.initialize_main_entrypoint() if args.run_from_cairo_pie is not None: # Add extra_segments. for segment_info in cairo_pie_input.metadata.extra_segments: runner.segments.add(size=segment_info.size) # Update the builtin runners' additional_data. for name, builtin_runner in runner.builtin_runners.items(): if name in cairo_pie_input.additional_data: builtin_runner.extend_additional_data( data=cairo_pie_input.additional_data[name], relocate_callback=lambda x: x, data_is_trusted=not args.secure_run) program_input = json.load(args.program_input) if args.program_input else {} runner.initialize_vm(hint_locals={'program_input': program_input}) try: if args.no_end: assert args.steps is not None, '--steps must be specified when running with --no-end.' else: additional_steps = 1 if args.proof_mode else 0 max_steps = steps_input - additional_steps if steps_input is not None else None runner.run_until_pc(end, max_steps=max_steps) if args.proof_mode: # Run one more step to make sure the last pc that was executed (rather than the pc # after it) is __end__. runner.run_for_steps(1) runner.original_steps = runner.vm.current_step if args.min_steps: runner.run_until_steps(args.min_steps) disable_trace_padding = False if steps_input is not None: runner.run_until_steps(steps_input) disable_trace_padding = True runner.end_run(disable_trace_padding=disable_trace_padding) except (VmException, AssertionError) as exc: if args.debug_error: print(f'Got an error:\n{exc}') ret_code = 1 else: raise exc if not args.no_end: runner.read_return_values() if not args.no_end and args.proof_mode: # Finalize important segments by correct size. runner.finalize_segments() if args.secure_run: verify_secure_runner(runner) if args.run_from_cairo_pie is not None: assert cairo_pie_input is not None assert cairo_pie_input == runner.get_cairo_pie(), \ 'The Cairo PIE input is not identical to the resulting Cairo PIE. ' \ 'This may indicate that the Cairo PIE was not generated by cairo_run.' if args.cairo_pie_output: runner.get_cairo_pie().to_file(args.cairo_pie_output) runner.relocate() if args.print_memory: runner.print_memory(relocated=args.relocate_prints) if args.print_output: runner.print_output() if args.print_info: runner.print_info(relocated=args.relocate_prints) # Skip builtin usage calculation if the execution stopped before reaching the end symbol. # Trying to calculate the builtin usage is likely to raise an exception and prevent the user # from opening the tracer. if args.proof_mode and not args.no_end: runner.print_builtin_usage() if trace_file is not None: field_bytes = math.ceil(program.prime.bit_length() / 8) write_binary_trace(trace_file, runner.relocated_trace) if memory_file is not None: field_bytes = math.ceil(program.prime.bit_length() / 8) write_binary_memory(memory_file, runner.relocated_memory, field_bytes) if args.air_public_input is not None: rc_min, rc_max = runner.get_perm_range_check_limits() write_air_public_input( layout=args.layout, public_input_file=args.air_public_input, memory=runner.relocated_memory, public_memory_addresses=runner.segments. get_public_memory_addresses(runner.segment_offsets), memory_segment_addresses=runner.get_memory_segment_addresses(), trace=runner.relocated_trace, rc_min=rc_min, rc_max=rc_max) if args.air_private_input is not None: assert args.trace_file is not None, \ '--trace_file must be set when --air_private_input is set.' assert args.memory_file is not None, \ '--memory_file must be set when --air_private_input is set.' json.dump( { 'trace_path': f'{os.path.abspath(trace_file.name)}', 'memory_path': f'{os.path.abspath(memory_file.name)}', **runner.get_air_private_input(), }, args.air_private_input, indent=4) print(file=args.air_private_input) args.air_private_input.flush() if debug_info_file is not None: json.dump(DebugInfo.Schema().dump(runner.get_relocated_debug_info()), debug_info_file) debug_info_file.flush() if args.tracer: CAIRO_TRACER = 'starkware.cairo.lang.tracer.tracer' subprocess.call( list( filter(None, [ sys.executable, '-m', CAIRO_TRACER, f'--program={args.program.name}', f'--trace={trace_file.name}', f'--memory={memory_file.name}', f'--air_public_input={args.air_public_input.name}' if args.air_public_input else None, f'--debug_info={debug_info_file.name}', ]))) if args.profile_output is not None: CAIRO_PROFILER = 'starkware.cairo.lang.tracer.profiler' subprocess.call( list( filter(None, [ sys.executable, '-m', CAIRO_PROFILER, f'--program={args.program.name}', f'--trace={trace_file.name}', f'--memory={memory_file.name}', f'--air_public_input={args.air_public_input.name}' if args.air_public_input else None, f'--debug_info={debug_info_file.name}', f'--profile_output={args.profile_output}', ]))) return ret_code