Exemple #1
0
    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.")
Exemple #2
0
    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()
Exemple #4
0
    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
Exemple #5
0
    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)
Exemple #7
0
    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
Exemple #8
0
    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
Exemple #9
0
    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()
Exemple #10
0
    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)
Exemple #12
0
    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
Exemple #13
0
    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!")
Exemple #14
0
 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)
Exemple #15
0
 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
Exemple #17
0
    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)
Exemple #18
0
    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)