def dispatch_worker(self, contract_address, execution_id, commitments): """ Dispatch worker thread to do the execution and proof generation. Args: contract_address: string, contract address. execution_id: int, the execution id for unique execution of this contract. commitments: commitments released by the given contract. """ if contract_address not in self.worker_pool.keys(): self.worker_pool[contract_address] = {} if execution_id not in self.worker_pool[ contract_address] or self.worker_pool[contract_address].ready( ): worker_thread = self.create_worker(contract_address, execution_id, commitments, self.execution_queue) self.worker_pool[contract_address][execution_id] = worker_thread worker_thread.start() if self.debug: LogUtils.info("Started the worker thread for contract@" + contract_address + " with execution_id: " + str(execution_id)) else: LogUtils.error( "Cannot create more than one worker for the same contract and same execution id at the same" "time.")
def setup(self, executor): """ The setup function before the thread starts to wait for the event arriving. Args: executor: the owner Executor. """ # Update the single commitment size info first. secs = self.chain_interface.get_single_execution_commitment_size( self.contract_address) executor.update_contract_info(self.contract_address, 'single_execution_commitment_size', secs) # For current Zokrates based executor, we need to download the required files before listening started. assert 'proving_key_path' in self.config assert 'code_path' in self.config assert 'abi_path' in self.config destination_paths = { 'proving_key_path': self.config['proving_key_path'], 'code_path': self.config['code_path'], 'abi_path': self.config['abi_path'] } if self.debug: LogUtils.info("Start to download required files") self.__downloader.download_required_files( self.contract_address, destination_paths, self.config['use_existing_data']) if self.debug: LogUtils.info("Start to compile required Zokrates code") assert 'zokrates_path' in self.config ZokratesCodeCompiler.compile_code(self.contract_address, self.config['zokrates_path'], self.config['code_path'], self.config['working_path'])
def wait_for_verify_and_settle_event(self, contract_id, execution_id): """ Wait for the verifyAndSettle event from contract and return the result. Args: contract_id: string, the unique identifier for the contract, it should be a key anchor for the proving key and contract within the proving key path and contract path. execution_id: int, the identity number for different execution of the same contract. Returns: Boolean, if verification succeeds, then return True, otherwise return False. """ retry_times = 3 while retry_times > 0: try: self.__chain_interface.wait_for_verify_and_settle_event( self.verify_and_settle_event, execution_id, self._put_result_into_queue, self) break except: retry_times = retry_times - 1 LogUtils.error('failed to wait for settle event, retry remaining %d time(s)' % retry_times) if retry_times > 0: traceback.print_exc() LogUtils.info('retrying after 5 seconds') sleep(5) else: raise return self.__verification_result.get()
def prepare_proof_generation(self, contract_id, execution_id): """ Prepare the proof generation. May include (but not limited to) the following processes: * Input data pre-processing. * Contract execution. * Witness generation. Args: contract_id: string, the unique identifier for the contract, it should be a key anchor for the proving key and contract within the proving key path and contract path. execution_id: string, the identity number for different execution of the same contract. Returns: Boolean, if preparation succeeded, return True, otherwise return False. """ command = self.__zokrates_path + " compute-witness -i " + path.join(self.__tmp_working_path, 'out') + ' -o ' + \ path.join(self.__tmp_working_path, 'witness') + " -a " + \ self.build_arguments(self.commitments, self.randoms, self.hashes) if self.debug: LogUtils.info("Run command: " + command) process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) process.communicate() #self._run_commands(['compute_witness'], # self.build_arguments(self.commitments, self.hashes)) if not self._check_generated_files('compute-witness'): raise PreparationException
def __init__(self, options, debug=False): """ Init the Executor. Args: options: dictionary, for configuration usage. debug: boolean, whether enable debug mode. """ threading.Thread.__init__(self) assert 'chain_config' in options assert 'poll_interval' in options self.check_options(options) self.options = options self.should_exit = False self.debug = debug if self.debug: LogUtils.info("Enable debug mode for Executor!") # List of registered contracts. self.registered_contracts = {} # The latest execution id for the register contract. self.registered_contracts_execution_count = {} self.registered_contract_verification_failed_result_count = {} self.registered_contract_verification_result_count = {} # The pool of the execution worker threads. self.worker_pool = {} # The pool of the listener threads. self.listener_pool = {} self.execution_queue = Queue() self.event_queue = Queue() self.submit_lock = Semaphore() # The map of the status information for all the workers self.__task_status = {}
def create_worker(self, contract_address, execution_id, commitments, execution_queue): """ Create the worker thread for the given execution. Args: contract_address: string, contract address. execution_id: int, the execution id for unique execution of this contract. commitments: commitments released by the given contract. execution_queue: the queue for storing the execution result from workers. Returns: ExecutionWorker. The worker for the given execution. """ execution_info = { 'contract_address': contract_address, 'execution_id': execution_id, 'zokrates_path': self.__zokrates_path, 'commitments': commitments, 'proving_key_path': self.__proving_key_path, 'code_path': self.__code_path, 'working_path': self.__working_folder_path, 'encryption_info': self.options['encryption_info'] } if self.debug: LogUtils.info("Enable debug mode for worker!") return ZokratesEthWorker(execution_info, self.__chain_config, execution_queue, self.submit_lock, self.debug)
def unregister_contract(self, contract_address): """ Unregister the contract which is already registered with this Executor. Args: contract_address: contract address. Returns: Boolean, if succeeded, return True, otherwise return False. """ if contract_address not in self.registered_contracts.keys(): return False self.update_worker_status(contract_address, TaskStatus.UNREGISTERING) # Stop listeners assert contract_address in self.listener_pool if not self.listener_pool[contract_address].ready(): self.listener_pool[contract_address].stop() self.listener_pool[contract_address].join() del self.listener_pool[contract_address] # Stop progressing workers if any if contract_address in self.worker_pool: self._clean_worker_pool(contract_address) del self.registered_contracts[contract_address] del self.registered_contracts_execution_count[contract_address] del self.registered_contract_verification_result_count[ contract_address] del self.registered_contract_verification_failed_result_count[ contract_address] self.unregister_clean_up(contract_address) self.update_worker_status(contract_address, TaskStatus.UNREGISTERED) if self.debug: LogUtils.info('Unregister contract@' + contract_address + ' succeeds') return True
def _prepare_files(self): """ Prepare the files required for the execution and proof generation of the worker. """ self._run_commands(['build_tmp_working_folder', 'copy_code', 'copy_proving_key', 'copy_variables']) if not self._check_generated_files('prepare'): if self.debug: LogUtils.info("Failed to copy required files and to finish Zokrates compiling!") raise PreparationException
def _run_commands(self, commands, append_str=''): """ List of command keys to run in the given order. Args: commands: [string], list of command keys. append_str: string to append after the commands. """ if self.debug: LogUtils.info("Run command: " + str(" && ".join(str(self.__commands[command]) for command in commands)) + append_str) process = subprocess.Popen(" && ".join(str(self.__commands[command]) for command in commands), shell=True, stdout=subprocess.PIPE) process.communicate()
def update_contract_info(self, contract_address, key, value): """ Update the contract info for target contract address. Args: contract_address: string, contract address or id. key: string, contract info key. value: obj, contract info value. """ if contract_address in self.registered_contracts: self.registered_contracts[contract_address][key] = value else: LogUtils.info("Cannot update non-registered contract info!")
def create_listener(self, listener_config, event_queue): """ Create the EventListener for the given contract address. Args: listener_config: dictionary, the configuration of listener. event_queue: Queue. Returns: EventListener thread. """ if self.debug: LogUtils.info("Enable debug mode for listener!") return EthEventListener(self, listener_config, event_queue, self.debug)
def _check_file_exists(self, file_list): """ Check the existence of the files in the list Args: file_list: [string], file path list to be checked. Returns: boolean, return True if all the files in the list exists, otherwise return False. """ for file_path in file_list: file = Path(file_path) if not file.exists(): if self.debug: LogUtils.info("Missing the required file: " + file_path) return False return True
def __init__(self, execution_info, execution_result_queue, submit_lock=None, debug=False): """ Init ZokratesWorker. Args: execution_info: dictionary, for all the execution and proof required information. execution_result_queue: queue, the queue maintained by the main thread. Worker thread put the execution result into the result queue for main thread to check. debug: boolean, debug flag. """ ExecutorWorker.__init__(self, execution_info, execution_result_queue, submit_lock, debug) try: self.__working_path = execution_info['working_path'] except KeyError: self.submit_execution_result(ExecutionResult.MISS_EXECUTION_INFO) try: self.__proving_key_path = execution_info['proving_key_path'] except KeyError: self.submit_execution_result(ExecutionResult.MISS_EXECUTION_INFO) try: self.__code_path = execution_info['code_path'] except KeyError: self.submit_execution_result(ExecutionResult.MISS_EXECUTION_INFO) try: self.__execution_id = execution_info['execution_id'] except KeyError: self.submit_execution_result(ExecutionResult.MISS_EXECUTION_INFO) try: self.__zokrates_path = execution_info['zokrates_path'] except KeyError: self.submit_execution_result(ExecutionResult.MISS_EXECUTION_INFO) try: self.__commitments = execution_info['commitments'] except KeyError: self.submit_execution_result(ExecutionResult.MISS_EXECUTION_INFO) # self.__field_bit_limit = int(pow(2, 128)) Use Zokrates prime instead self.__field_bit_limit = 21888242871839275222246405745257275088548364400416034343698204186575808495616 # commands need to be run by the workers. self.__commands = {} self._build_commands() if self.debug: LogUtils.info("Start to prepare files for ZokratesWorker!") self._prepare_files() if self.debug: LogUtils.info("Finished file preparation!")
def _clean_worker_pool(self, contract_address): cleaned_worker_cnt = 0 if contract_address in list(self.worker_pool.keys()): for execution_id in list( self.worker_pool[contract_address].keys()): if not self.worker_pool[contract_address][execution_id].ready( ): LogUtils.error( "Error, worker thread is still live for contract@" + contract_address + ", execution_id: " + str(execution_id) + ". Force killed") self.worker_pool[contract_address][execution_id].stop() self.worker_pool[contract_address][execution_id].join() cleaned_worker_cnt += 1 del self.worker_pool[contract_address][execution_id] del self.worker_pool[contract_address] if self.debug: LogUtils.info('Cleaned ' + str(cleaned_worker_cnt) + ' worker tasks for @' + contract_address)
def _run(self): # Before start to wait, can do some setup, like file downloading... if self.debug: LogUtils.info("Start setup before listening.") try: self.setup(self.__executor) except InvalidAddress as e: if self.debug: LogUtils.info("Setup failed: " + str(e)) event_info = { 'contract_address': self.contract_address, 'status': EventListenerStatus.SETUP_FAILED, 'debug_msg': str(e) } self.queue.put(event_info) return except SetupException as e: if self.debug: LogUtils.info("Setup failed: " + str(e)) event_info = { 'contract_address': self.contract_address, 'status': EventListenerStatus.SETUP_FAILED, 'debug_msg': str(e) } self.queue.put(event_info) return event_info = { 'contract_address': self.contract_address, 'status': EventListenerStatus.SETUP_SUCCEEDED } self.queue.put(event_info) if self.debug: LogUtils.info( "Setup finished, start to listening on CommitmentOpen") while not self.__should_exit: self.wait_for_commitment_open(self.__poll_interval, self._put_event_into_queue)
def get_options(self): LogUtils.info( "\nStart the Origo Executor with the following configurations:") for key, value in self.options.items(): LogUtils.info(key + ':\t\t' + str(value)) return self.options
def _run(self): """ Finish the execution and proof generation and submit all the result back to block chain. """ if self.debug: LogUtils.info("Start to check commitment validation") try: commitments, randoms, self.hashes = self._check_commitments_validation( self.__encrypted_commitments) except CommitmentValidationFailed: self.submit_execution_result(ExecutionResult.INVALID_COMMITMENTS) return if self.debug: LogUtils.info("Start to decrypt") try: if self.debug: LogUtils.info("Input encrypted commitment:" + str(commitments)) LogUtils.info("Input encrypted random:" + str(randoms)) # find out the data that should be skipped: if the commitment, random and hash are all the same value, then # the decryption and hash check will be skipped for those input. skipped_indices = self._find_skipped_commitment_indices( commitments, randoms, self.hashes) decrypted_commitments_and_randoms = self.decrypt_inputs( commitments + randoms, skipped_indices) half = int(len(decrypted_commitments_and_randoms) / 2) self.commitments = decrypted_commitments_and_randoms[:half] self.randoms = decrypted_commitments_and_randoms[half:] if self.debug: LogUtils.info("Input decrypted commitment:" + str(self.commitments)) LogUtils.info("Input decrypted random:" + str(self.randoms)) except DecryptionException: self.submit_execution_result(ExecutionResult.FAILED_TO_DECRYPT) return if self.debug: LogUtils.info("Start to check sha256 of commitments") try: self.check_commitments(self.commitments, self.randoms, self.hashes, skipped_indices) except CommitmentHashNotMatch: self.submit_execution_result(ExecutionResult.HASH_NOT_MATCH) return if self.debug: LogUtils.info("Start to prepare proof") try: self.prepare_proof_generation(self.contract_address, self.__execution_id) except PreparationException: self.submit_execution_result(ExecutionResult.FAILED_TO_PREPARE) return if self.debug: LogUtils.info("Start to generate_proof") try: output, proof = self.generate_proof(self.contract_address, self.__execution_id) if self.debug: LogUtils.info('output is:' + str(output)) LogUtils.info('proof is:' + str(proof)) except ProofException: self.submit_execution_result( ExecutionResult.FAILED_TO_GENERATE_PROOF) return if self.debug: LogUtils.info("Start to submit proof") try: if self.submit_lock and self.submit_lock.acquire(): try: self.submit_proof_to_chain(self.contract_address, self.__execution_id, output, proof) finally: self.submit_lock.release() else: self.submit_proof_to_chain(self.contract_address, self.__execution_id, output, proof) except SubmissionException: self.submit_execution_result( ExecutionResult.FAILED_TO_SUBMIT_PROOF) return if self.debug: LogUtils.info( "Finished proof submission, waiting for VerifyAndSettleEvent") verification_result = self.wait_for_verify_and_settle_event( self.contract_address, self.__execution_id) if verification_result: if self.debug: LogUtils.info("Online verification succeeded.") self.submit_execution_result(ExecutionResult.SUCCESS) else: if self.debug: LogUtils.info("Online verification failed.") self.submit_execution_result(ExecutionResult.FAIL)
def run(self): """ Listening on all the registered contracts' commitment opening event. If the event is triggered, then dispatch an ExecutorWorker thread to finish the proof. """ while not self.should_exit: if not self.event_queue.empty(): # Check the listener setup status first. contract_address, commitments, status, debug_msg = self.handle_event_from_queue( ) if status == EventListenerStatus.SETUP_SUCCEEDED: if self.debug: LogUtils.info( "Status update for contract@" + contract_address + ": " + EventListenerStatus.STATUS_EXPLANATION[status]) self.update_worker_status(contract_address, TaskStatus.LISTENING) elif status == EventListenerStatus.SETUP_FAILED: if self.debug: LogUtils.error( ("Status update for contract@" + contract_address + ": " + EventListenerStatus.STATUS_EXPLANATION[status])) self.update_worker_status(contract_address, TaskStatus.FAILED_TO_REGISTER, debug_msg) elif commitments is not None: if self.debug: LogUtils.info("Received commitment with length [" + str(len(commitments)) + "] from contract@" + contract_address) single_execution_commitment_size = self._get_single_execution_commitment_size( contract_address) if self.debug: LogUtils.info( "single_execution_commitment_size for contract:" + contract_address + " is [" + str(single_execution_commitment_size) + "]") if single_execution_commitment_size is None: LogUtils.error( "No single_execution_commitment_size found!") self.update_worker_status(contract_address, TaskStatus.FINISHED) else: single_execution_commitment_length = single_execution_commitment_size * \ ExecutorConstants.ENCRYPTED_DATA_SIZE if len(commitments ) % single_execution_commitment_length != 0: LogUtils.error( "Invalid commitment length, cannot be divided by single_execution_commitment" " size") self.update_worker_status(contract_address, TaskStatus.FINISHED) else: execution_num = int( len(commitments) / single_execution_commitment_length) self.registered_contracts_execution_count[ contract_address] = execution_num self.update_worker_status(contract_address, TaskStatus.EXECUTING) for execution_id in range(execution_num): single_execution_commitments = \ commitments[execution_id * single_execution_commitment_length: (execution_id + 1) * single_execution_commitment_length] if self.debug: LogUtils.info( "Execute commitment[" + str(single_execution_commitments) + "] from contract@" + contract_address) self.dispatch_worker( contract_address, execution_id, single_execution_commitments) # Wait for 10 second before staring another worker thread for a new task to avoid too # heavy job. time.sleep(10) # Check the result queue. if not self.execution_queue.empty(): execution_result = self.execution_queue.get() if self.debug: LogUtils.info('Received execution result' + str(execution_result)) contract_address = execution_result['contract_address'] self.registered_contract_verification_result_count[ contract_address] += 1 # If there is any failed execution, need to count down. if execution_result[ 'execution_result'] != ExecutionResult.SUCCESS: execution_id = execution_result['execution_id'] self.registered_contract_verification_failed_result_count[contract_address][execution_id] = \ str(execution_result['execution_result']) if execution_result['debug_msg'] is not None: self.registered_contract_verification_failed_result_count[contract_address][execution_id] += \ ' (' + execution_result['debug_msg'] + ')' # always update the worker status with EXECUTING after received each execution id's result. self.update_worker_status(contract_address, TaskStatus.EXECUTING) if self.registered_contract_verification_result_count[contract_address] == \ self.registered_contracts_execution_count[contract_address]: self.update_worker_status(contract_address, TaskStatus.FINISHED) # Once finished, cleanup all the execution counters. self.registered_contract_verification_result_count[ contract_address] = 0 self.registered_contract_verification_failed_result_count[ contract_address].clear() self.registered_contracts_execution_count[ contract_address] = 0 if self.debug: LogUtils.info('Start to clean worker pool for @' + contract_address) self._clean_worker_pool(contract_address) time.sleep(1)