def validate_task_to_compute(task_to_compute: message.TaskToCompute) -> None: if not isinstance(task_to_compute, message.TaskToCompute): raise ConcentValidationError( f"Expected TaskToCompute instead of {type(task_to_compute).__name__}.", error_code=ErrorCode.MESSAGE_INVALID, ) if any( map(lambda x: x is None, [ getattr(task_to_compute, attribute) for attribute in [ 'compute_task_def', 'provider_public_key', 'requestor_public_key' ] ])): raise ConcentValidationError( "Invalid TaskToCompute", error_code=ErrorCode.MESSAGE_WRONG_FIELDS, ) validate_compute_task_def(task_to_compute.compute_task_def) validate_hex_public_key(task_to_compute.provider_public_key, 'provider_public_key') validate_hex_public_key(task_to_compute.requestor_public_key, 'requestor_public_key') validate_secure_hash_algorithm(task_to_compute.package_hash) validate_positive_integer_value(task_to_compute.price)
def validate_compute_task_def(compute_task_def: message.tasks.ComputeTaskDef) -> None: string_fields = ["output_format", "scene_file"] other_mandatory_fields = ["frames"] validate_value_is_int_convertible_and_positive(compute_task_def['deadline']) validate_id_value(compute_task_def['task_id'], 'task_id') validate_id_value(compute_task_def['subtask_id'], 'subtask_id') extra_data = compute_task_def.get("extra_data") if extra_data is None: raise ConcentValidationError( "'extra_data' is missing in ComputeTaskDef", ErrorCode.MESSAGE_INVALID ) for mandatory_data in string_fields + other_mandatory_fields: if mandatory_data not in extra_data: raise ConcentValidationError( f"{mandatory_data} is missing in ComputeTaskDef", ErrorCode.MESSAGE_INVALID ) validate_frames(extra_data["frames"]) for string_field in string_fields: if not isinstance(extra_data[string_field], str): raise ConcentValidationError( f"{string_field} should be string", ErrorCode.MESSAGE_VALUE_NOT_STRING ) validate_scene_file(extra_data['scene_file'])
def validate_task_to_compute(task_to_compute: message.TaskToCompute) -> None: if not isinstance(task_to_compute, message.TaskToCompute): raise ConcentValidationError( f"Expected TaskToCompute instead of {type(task_to_compute).__name__}.", error_code=ErrorCode.MESSAGE_INVALID, ) if any( map(lambda x: x is None, [ getattr(task_to_compute, attribute) for attribute in [ 'compute_task_def', 'provider_public_key', 'requestor_public_key' ] ])): raise ConcentValidationError( "Invalid TaskToCompute", error_code=ErrorCode.MESSAGE_WRONG_FIELDS, ) if not task_to_compute.verify_all_promissory_notes( deposit_contract_address=settings.GNT_DEPOSIT_CONTRACT_ADDRESS): raise ConcentValidationError( "The signature of the PromissiryNote for the Provider, which should be signed by Requestor is incorrect", error_code=ErrorCode.MESSAGE_INVALID, ) validate_compute_task_def(task_to_compute.compute_task_def) validate_hex_public_key(task_to_compute.provider_public_key, 'provider_public_key') validate_hex_public_key(task_to_compute.requestor_public_key, 'requestor_public_key') validate_secure_hash_algorithm(task_to_compute.package_hash) validate_positive_task_price(task_to_compute.price)
def validate_samples(samples: int) -> None: if not isinstance(samples, int): raise ConcentValidationError( 'samples must be integer', ErrorCode.MESSAGE_INVALID, ) if samples < 0: raise ConcentValidationError( 'samples must be bigger or equal zero', ErrorCode.MESSAGE_INVALID, )
def validate_scene_file(scene_file: str) -> None: if not scene_file.endswith(SCENE_FILE_EXTENSION): raise ConcentValidationError( f'{scene_file} must ends with {SCENE_FILE_EXTENSION} filename extension', ErrorCode.MESSAGE_INVALID) if not any( scene_file.startswith(file_path) for file_path in VALID_SCENE_FILE_PREFIXES): raise ConcentValidationError( f'{scene_file} path must starts with one of {VALID_SCENE_FILE_PREFIXES} paths', ErrorCode.MESSAGE_INVALID)
def validate_uuid(id_: str) -> None: if not isinstance(id_, str): raise ConcentValidationError( f'ID must be string with maximum {MESSAGE_TASK_ID_MAX_LENGTH} characters length', error_code=ErrorCode.MESSAGE_WRONG_UUID_TYPE) try: UUID(id_, version=4) except ValueError: raise ConcentValidationError( 'ID must be a UUID derivative.', error_code=ErrorCode.MESSAGE_WRONG_UUID_VALUE, )
def get_validated_client_public_key_from_client_message(golem_message: message.base.Message): if isinstance(golem_message, message.concents.ForcePayment): if ( isinstance(golem_message.subtask_results_accepted_list, list) and len(golem_message.subtask_results_accepted_list) > 0 ): task_to_compute = golem_message.subtask_results_accepted_list[0].task_to_compute else: raise ConcentValidationError( "subtask_results_accepted_list must be a list type and contains at least one message", error_code=ErrorCode.MESSAGE_VALUE_WRONG_LENGTH, ) elif isinstance(golem_message, message.tasks.TaskMessage): if not golem_message.is_valid(): raise GolemMessageValidationError( "Golem message invalid", error_code=ErrorCode.MESSAGE_INVALID ) task_to_compute = golem_message.task_to_compute else: raise ConcentValidationError( "Unknown message type", error_code=ErrorCode.MESSAGE_UNKNOWN, ) if task_to_compute is not None: if isinstance(golem_message, ( message.ForceReportComputedTask, message.concents.ForceSubtaskResults, message.concents.ForcePayment, message.concents.SubtaskResultsVerify, )): client_public_key = task_to_compute.provider_public_key validate_hex_public_key(client_public_key, 'provider_public_key') elif isinstance(golem_message, ( message.AckReportComputedTask, message.RejectReportComputedTask, message.concents.ForceGetTaskResult, message.concents.ForceSubtaskResultsResponse, )): client_public_key = task_to_compute.requestor_public_key validate_hex_public_key(client_public_key, 'requestor_public_key') else: raise ConcentValidationError( "Unknown message type", error_code=ErrorCode.MESSAGE_UNKNOWN, ) return hex_to_bytes_convert(client_public_key) return None
def validate_subtask_results_verify(msg: SubtaskResultsVerify, deposit_contract_address: str) -> None: if not msg.verify_concent_promissory_note(deposit_contract_address): raise ConcentValidationError( "The signature of the PromissiryNote for Concent is incorrect", error_code=ErrorCode.MESSAGE_INVALID, )
def validate_blender_output_format(output_format: str) -> None: if adjust_format_name( output_format ) not in BlenderSubtaskDefinition.OutputFormat.__members__.keys(): raise ConcentValidationError( f'Unsupported Blender format!', error_code=ErrorCode.MESSAGE_VALUE_NOT_ALLOWED)
def validate_all_messages_identical( golem_messages_list: List[message.Message]) -> None: assert isinstance(golem_messages_list, list) assert len(golem_messages_list) >= 1 assert all( isinstance(golem_message, message.Message) for golem_message in golem_messages_list) assert len( set(type(golem_message) for golem_message in golem_messages_list)) == 1 base_golem_message = golem_messages_list[0] for i, golem_message in enumerate(golem_messages_list[1:], start=1): for slot in base_golem_message.__slots__: if getattr(base_golem_message, slot) != getattr( golem_message, slot): raise ConcentValidationError( '{} messages are not identical. ' 'There is a difference between messages with index 0 on passed list and with index {}' 'The difference is on field {}: {} is not equal {}'.format( type(base_golem_message).__name__, i, slot, getattr(base_golem_message, slot), getattr(golem_message, slot), ), error_code=ErrorCode.MESSAGES_NOT_IDENTICAL, )
def validate_golem_message_subtask_results_rejected(subtask_results_rejected: message.tasks.SubtaskResultsRejected): if not isinstance(subtask_results_rejected, message.tasks.SubtaskResultsRejected): raise ConcentValidationError( "subtask_results_rejected should be of type: SubtaskResultsRejected", error_code=ErrorCode.MESSAGE_INVALID, ) validate_task_to_compute(subtask_results_rejected.report_computed_task.task_to_compute)
def validate_positive_integer_value(value): validate_expected_value_type(value, 'value', int) if value < 0: raise ConcentValidationError( "Value cannot be an negative value", error_code=ErrorCode.MESSAGE_VALUE_NEGATIVE, )
def validate_value_is_int_convertible(value: int) -> None: try: int(value) except (ValueError, TypeError): raise ConcentValidationError( "Wrong type, expected a value that can be converted to an integer.", error_code=ErrorCode.MESSAGE_VALUE_NOT_INTEGER, )
def validate_report_computed_task_time_window(report_computed_task): assert isinstance(report_computed_task, message.ReportComputedTask) if report_computed_task.timestamp < report_computed_task.task_to_compute.timestamp: raise ConcentValidationError( "ReportComputedTask timestamp is older then nested TaskToCompute.", error_code=ErrorCode.MESSAGE_TIMESTAMP_TOO_OLD, )
def validate_expected_value_type( value, value_name: str, expected_type, ): if not isinstance(value, expected_type): raise ConcentValidationError( f"{value_name} must be {expected_type.__name__}.", error_code=ErrorCode.MESSAGE_VALUE_WRONG_TYPE, )
def validate_id_value(value, field_name): validate_expected_value_type(value, field_name, str) if value == '': raise ConcentValidationError( "{} cannot be blank.".format(field_name), error_code=ErrorCode.MESSAGE_VALUE_BLANK, ) if len(value) > MESSAGE_TASK_ID_MAX_LENGTH: raise ConcentValidationError( "{} cannot be longer than {} chars.".format(field_name, MESSAGE_TASK_ID_MAX_LENGTH), error_code=ErrorCode.MESSAGE_VALUE_WRONG_LENGTH, ) if VALID_ID_REGEX.fullmatch(value) is None: raise ConcentValidationError( f'{field_name} must contain only alphanumeric chars.', error_code=ErrorCode.MESSAGE_VALUE_NOT_ALLOWED, )
def validate_key_with_desired_parameters(key_name: str, key_value: Union[bytes, str], expected_type: Any, expected_length: int) -> None: validate_expected_value_type(key_value, key_name, expected_type) if len(key_value) != expected_length: raise ConcentValidationError( "The length of {} must be exactly {} characters.".format( key_name, expected_length), error_code=ErrorCode.MESSAGE_VALUE_WRONG_LENGTH, )
def validate_resolution(resolution: List[float]) -> None: if not isinstance(resolution, list): raise ConcentValidationError( 'resolution is not a list', ErrorCode.MESSAGE_INVALID, ) if len(resolution) != 2: raise ConcentValidationError( 'resolution must contain exactly 2 values', ErrorCode.MESSAGE_INVALID, ) for element in resolution: if not isinstance(element, int): raise ConcentValidationError( 'resolution is not a list of integers', ErrorCode.MESSAGE_INVALID, ) if element <= 0: raise ConcentValidationError( 'resolution must contain positive integers', ErrorCode.MESSAGE_INVALID, )
def validate_blender_script_parameters(extra_data: Any) -> None: mandatory_fields = ['resolution', 'use_compositing', 'samples', 'crops'] for field in mandatory_fields: if field not in extra_data: raise ConcentValidationError( f"{field} is missing in 'extra_data' in ComputeTaskDef", ErrorCode.MESSAGE_INVALID, ) validate_resolution(extra_data['resolution']) validate_use_compositing(extra_data['use_compositing']) validate_samples(extra_data['samples']) validate_crops(extra_data['crops'])
def validate_value_is_int_convertible_and_positive(value): """ Checks if value is an integer. If not, tries to cast it to an integer. Then checks if value is non-negative. """ if not isinstance(value, int): try: value = int(value) except (ValueError, TypeError): raise ConcentValidationError( "Wrong type, expected a value that can be converted to an integer.", error_code=ErrorCode.MESSAGE_VALUE_NOT_INTEGER, ) validate_positive_integer_value(value)
def validate_crops(crops: List[Dict[str, List[float]]]) -> None: if not isinstance(crops, list): raise ConcentValidationError( "crops field in 'extra_data' must be a list", ErrorCode.MESSAGE_INVALID, ) if len(crops) != 1: raise ConcentValidationError( "crops field must contain exactly one dict with borders details", ErrorCode.MESSAGE_INVALID, ) crop_details = crops[0] mandatory_fields = ['borders_x', 'borders_y'] for field in mandatory_fields: if field not in crop_details: raise ConcentValidationError( f'{field} does not exist in crop_details dict', ErrorCode.MESSAGE_INVALID, ) if not isinstance(crop_details[field], list): raise ConcentValidationError( f'{field} is not a list', ErrorCode.MESSAGE_INVALID, ) if len(crop_details[field]) != 2: raise ConcentValidationError(f'{field} must have exactly 2 fields', ErrorCode.MESSAGE_INVALID) for border in crop_details[field]: if not isinstance(border, float): raise ConcentValidationError( f'{field} must contain float borders', ErrorCode.MESSAGE_INVALID, ) if border > 1.0 or border < 0.0: raise ConcentValidationError( f'Border in {field} must has value between 0.0 and 1.0', ErrorCode.MESSAGE_INVALID, ) if crop_details[field][0] >= crop_details[field][1]: raise ConcentValidationError( f'Minimum of {field} can not be bigger or equal maximum', ErrorCode.MESSAGE_INVALID, )
def validate_all_messages_identical( golem_messages_list: List[message.Message]) -> None: assert isinstance(golem_messages_list, list) assert len(golem_messages_list) >= 1 assert all( isinstance(golem_message, message.Message) for golem_message in golem_messages_list) assert len( set(type(golem_message) for golem_message in golem_messages_list)) == 1 base_golem_message = golem_messages_list[0] for golem_message in golem_messages_list[1:]: if base_golem_message != golem_message: raise ConcentValidationError( f'Messages {base_golem_message.__class__.__name__} are not identical', error_code=ErrorCode.MESSAGES_NOT_IDENTICAL, )
def validate_scene_file(scene_file): if not scene_file.endswith('.blend'): raise ConcentValidationError( f'{scene_file} must ends with ".blend" filename extension', ErrorCode.MESSAGE_INVALID )
def validate_use_compositing(use_compositing: bool) -> None: if not isinstance(use_compositing, bool): raise ConcentValidationError( 'use_compositing must be a boolean', ErrorCode.MESSAGE_INVALID, )