Esempio n. 1
0
    def send_eth(self,
                 uuid: UUID,
                 amount_wei: int,
                 recipient_address: str,
                 signing_address: Optional[str] = None, encrypted_private_key: Optional[str] = None,
                 prior_tasks: Optional[UUIDList] = None,
                 posterior_tasks: Optional[UUIDList] = None):
        """
        The main entrypoint sending eth.

        :param uuid: the celery generated uuid for the task
        :param amount_wei: the amount in WEI to send
        :param recipient_address: the recipient address
        :param signing_address: address of the wallet signing the txn
        :param encrypted_private_key: private key of the wallet making the transaction, encrypted using key from settings
        :param prior_tasks: a list of task uuids that must succeed before this task will be attempted
        :param posterior_tasks: a uuid list of tasks for which this task must succeed before they will be attempted
        :return: task_id
        """

        signing_wallet_obj = self.get_signing_wallet_object(signing_address, encrypted_private_key)

        task = self.persistence_interface.create_send_eth_task(uuid,
                                                               signing_wallet_obj,
                                                               recipient_address, amount_wei,
                                                               prior_tasks,
                                                               posterior_tasks)

        # Attempt Create Async Transaction
        signature(utils.eth_endpoint('_attempt_transaction'), args=(task.uuid,)).delay()
Esempio n. 2
0
    def deploy_contract(
            self,
            uuid: UUID,
            contract_name: str,
            args: Optional[tuple] = None, kwargs: Optional[dict] = None,
            signing_address: Optional[str] = None, encrypted_private_key: Optional[str]=None,
            gas_limit: Optional[int] = None,
            prior_tasks: Optional[UUIDList] = None
    ):
        """
        The main deploy contract entrypoint for the processor.

        :param uuid: the celery generated uuid for the task
        :param contract_name: System will attempt to fetched abi and bytecode from this
        :param args: arguments for the constructor
        :param kwargs: keyword arguments for the constructor
        :param signing_address: address of the wallet signing the txn
        :param encrypted_private_key: private key of the wallet making the transaction, encrypted using key from settings
        :param gas_limit: limit on the amount of gas txn can use. Overrides system default
        :param prior_tasks: a list of task uuid that must succeed before this task will be attempted
        """

        signing_wallet_obj = self.get_signing_wallet_object(signing_address, encrypted_private_key)

        task = self.persistence_interface.create_deploy_contract_task(uuid,
                                                                      signing_wallet_obj,
                                                                      contract_name,
                                                                      args, kwargs,
                                                                      gas_limit,
                                                                      prior_tasks)

        # Attempt Create Async Transaction
        signature(utils.eth_endpoint('_attempt_transaction'), args=(task.uuid,)).delay()
Esempio n. 3
0
def transaction_task(signing_address,
                     contract_address,
                     contract_type,
                     func,
                     args=None,
                     gas_limit=None,
                     prior_tasks=None,
                     reverses_task=None):

    kwargs = {
        'signing_address': signing_address,
        'contract_address': contract_address,
        'abi_type': contract_type,
        'function': func,
        'args': args,
        'prior_tasks': prior_tasks,
        'reverses_task': reverses_task
    }

    if gas_limit:
        kwargs['gas_limit'] = gas_limit

    sig = signature(utils.eth_endpoint('transact_with_contract_function'),
                    kwargs=kwargs)

    return utils.execute_task(sig)
Esempio n. 4
0
    def topup_if_required(self, wallet, posterior_task_uuid):
        balance = self.w3.eth.getBalance(wallet.address)

        wei_topup_threshold = wallet.wei_topup_threshold
        wei_target_balance = wallet.wei_target_balance or 0

        if balance <= wei_topup_threshold and wei_target_balance > balance:
            sig = signature(utils.eth_endpoint('send_eth'),
                            kwargs={
                                'signing_address':
                                config.MASTER_WALLET_ADDRESS,
                                'amount_wei': wei_target_balance - balance,
                                'recipient_address': wallet.address,
                                'prior_tasks': [],
                                'posterior_tasks': [posterior_task_uuid]
                            })

            task_uuid = utils.execute_task(sig)

            self.persistence_interface.set_wallet_last_topup_task_uuid(
                wallet.address, task_uuid)

            return task_uuid

        return None
Esempio n. 5
0
    def check_transaction_response(self, celery_task, transaction_id):
        def transaction_response_countdown():
            t = lambda retries: ETH_CHECK_TRANSACTION_BASE_TIME * 2**retries

            # If the system has been longer than the max retry period
            # if previous_result:
            #     submitted_at = datetime.strptime(previous_result['submitted_date'], "%Y-%m-%d %H:%M:%S.%f")
            #     if (datetime.utcnow() - submitted_at).total_seconds() > ETH_CHECK_TRANSACTION_RETRIES_TIME_LIMIT:
            #         if self.request.retries != self.max_retries:
            #             self.request.retries = self.max_retries - 1
            #
            #         return 0

            return t(celery_task.request.retries)

        try:
            transaction_object = self.persistence_interface.get_transaction(
                transaction_id)

            task = transaction_object.task

            transaction_hash = transaction_object.hash

            result = self.check_transaction_hash(transaction_hash)

            self.persistence_interface.update_transaction_data(
                transaction_id, result)

            status = result.get('status')

            print(
                f'Status for transaction {transaction_object.id} of task UUID {task.uuid} is:'
                f'\n {status}')

            if status == 'SUCCESS':

                unstarted_posteriors = self.get_unstarted_posteriors(task)

                for dep_task in unstarted_posteriors:
                    print('Starting posterior task: {}'.format(dep_task.uuid))
                    signature(utils.eth_endpoint('_attempt_transaction'),
                              args=(dep_task.uuid, )).delay()

                self.persistence_interface.set_task_status_text(
                    task, 'SUCCESS')

            if status == 'PENDING':
                celery_task.request.retries = 0
                raise Exception("Need Retry")

            if status == 'FAILED':
                self.new_transaction_attempt(task)

        except TaskRetriesExceededError as e:
            pass

        except Exception as e:
            print(e)
            celery_task.retry(countdown=transaction_response_countdown())
Esempio n. 6
0
def send_eth_task(signing_address, amount_wei, recipient_address):
    sig = signature(utils.eth_endpoint('send_eth'),
                    kwargs={
                        'signing_address': signing_address,
                        'amount_wei': amount_wei,
                        'recipient_address': recipient_address
                    })

    return utils.execute_task(sig)
Esempio n. 7
0
def synchronous_call(contract_address, contract_type, func, args=None):
    call_sig = signature(utils.eth_endpoint('call_contract_function'),
                         kwargs={
                             'contract_address': contract_address,
                             'abi_type': contract_type,
                             'function': func,
                             'args': args,
                         })

    return utils.execute_synchronous_celery(call_sig)
Esempio n. 8
0
def deploy_contract_task(signing_address, contract_name, args=None, prior_tasks=None):
    deploy_sig = signature(
        utils.eth_endpoint('deploy_contract'),
        kwargs={
            'signing_address': signing_address,
            'contract_name': contract_name,
            'args': args,
            'prior_tasks': prior_tasks
        })

    return utils.execute_task(deploy_sig)
Esempio n. 9
0
def await_task_success(task_uuid, timeout=None, poll_frequency=0.5):
    elapsed = 0
    print(f'Awaiting success for task uuid: {task_uuid}')
    while timeout is None or elapsed <= timeout:
        sig = signature(utils.eth_endpoint('get_task'),
                        kwargs={'task_uuid': task_uuid})

        task = utils.execute_synchronous_celery(sig)

        if task and task['status'] == 'SUCCESS':
            return task
        else:
            sleep(poll_frequency)
            elapsed += poll_frequency

    raise TimeoutError
Esempio n. 10
0
    def new_transaction_attempt(self, task):
        number_of_attempts_this_round = abs(
            len(task.transactions) - self.task_max_retries * (task.previous_invocations or 0)
        )
        if number_of_attempts_this_round >= self.task_max_retries:
            print(f"Maximum retries exceeded for task {task.uuid}")

            if task.status_text != 'SUCCESS':
                self.persistence_interface.set_task_status_text(task, 'FAILED')

            raise TaskRetriesExceededError

        else:
            signature(utils.eth_endpoint('_attempt_transaction'),
                      args=(task.uuid,)).apply_async(
                countdown=RETRY_TRANSACTION_BASE_TIME * 4 ** number_of_attempts_this_round
            )
Esempio n. 11
0
def topup_wallets():
    wallets = persistence_interface.get_all_wallets()

    for wallet in wallets:
        if (wallet.wei_topup_threshold or 0) > 0:

            last_topup_task_uuid = wallet.last_topup_task_uuid

            if last_topup_task_uuid:
                task = persistence_interface.get_task_from_uuid(
                    last_topup_task_uuid)

                if task and task.status in ['PENDING', 'UNSTARTED']:
                    return

            signature(utils.eth_endpoint('topup_wallet_if_required'),
                      kwargs={
                          'address': wallet.address
                      }).delay()
Esempio n. 12
0
    def transact_with_contract_function(
            self,
            uuid: UUID,
            contract_address: str,
            abi_type: str,
            function_name: str,
            args: Optional[tuple] = None,
            kwargs: Optional[dict] = None,
            signing_address: Optional[str] = None,
            encrypted_private_key: Optional[str] = None,
            gas_limit: Optional[int] = None,
            prior_tasks: Optional[UUIDList] = None,
            reserves_task: Optional[UUID] = None):
        """
        The main transaction entrypoint for the processor.
        :param uuid: the celery generated uuid for the task
        :param contract_address: the address of the contract for the function
        :param abi_type: the type of ABI for the contract being called
        :param function_name: name of the function
        :param args: arguments for the function
        :param kwargs: keyword arguments for the function
        :param signing_address: address of the wallet signing the txn
        :param encrypted_private_key: private key of the wallet making the transaction, encrypted using key from settings
        :param gas_limit: limit on the amount of gas txn can use. Overrides system default
        :param prior_tasks: a list of task uuids that must succeed before this task will be attempted,
        :param reserves_task: the uuid of a task that this task reverses. can only be a transferFrom
        :return: task_id
        """

        signing_wallet_obj = self.get_signing_wallet_object(
            signing_address, encrypted_private_key)

        task = self.persistence_interface.create_function_task(
            uuid, signing_wallet_obj, contract_address, abi_type,
            function_name, args, kwargs, gas_limit, prior_tasks, reserves_task)

        # Attempt Create Async Transaction
        signature(utils.eth_endpoint('_attempt_transaction'),
                  args=(task.uuid, )).delay()
Esempio n. 13
0
def topup_if_required(address):
    balance = w3.eth.getBalance(address)

    wallet = persistence_module.get_wallet_by_address(address)
    wei_topup_threshold = wallet.wei_topup_threshold
    wei_target_balance = wallet.wei_target_balance or 0

    if balance <= wei_topup_threshold and wei_target_balance > balance:
        sig = signature(utils.eth_endpoint('send_eth'),
                        kwargs={
                            'signing_address': config.MASTER_WALLET_ADDRESS,
                            'amount_wei': wei_target_balance - balance,
                            'recipient_address': address,
                            'prior_tasks': []
                        })

        task_uuid = utils.execute_task(sig)

        persistence_module.set_wallet_last_topup_task_uuid(address, task_uuid)

        return task_uuid

    return None
Esempio n. 14
0
 def _retry_task(self, task):
     self.persistence_interface.increment_task_invokations(task)
     signature(utils.eth_endpoint('_attempt_transaction'), args=(task.uuid,)).delay()
Esempio n. 15
0
    def attempt_transaction(self, task_uuid):

        task = self.persistence_interface.get_task_from_uuid(task_uuid)

        unsatisfied_prior_tasks = self.get_unsatisfied_prior_tasks(task)

        if len(unsatisfied_prior_tasks) > 0:
            print('Skipping {}: prior tasks {} unsatisfied'.format(
                task.id,
                [f'{u.id} ({u.uuid})' for u in unsatisfied_prior_tasks]))
            return

        topup_uuid = self.topup_if_required(task.signing_wallet, task_uuid)

        if topup_uuid:
            print(f'Skipping {task.id}: Topup required')
            return

        # This next section is designed to ensure that we don't have two transactions running for the same task
        # at the same time. Under normal conditions this doesn't happen, but the 'retry failed transactions' can
        # get us there if it's called twice in quick succession. We use a mutex over the next lines
        # to prevent two processes both passing the 'current_status' test and then creating a transaction

        have_lock = False
        lock = self.red.lock(f'TaskID-{task.id}', timeout=10)
        try:
            have_lock = lock.acquire(blocking_timeout=1)
            if have_lock:

                current_status = task.status
                if current_status in ['SUCCESS', 'PENDING']:
                    print(f'Skipping {task.id}: task status is currently {current_status}')
                    return
                transaction_obj = self.persistence_interface.create_blockchain_transaction(task_uuid)
            else:
                print(f'Skipping {task.id}: Failed to aquire lock')
                return

        finally:
            if have_lock:
                lock.release()

        task_object = self.persistence_interface.get_task_from_uuid(task_uuid)

        number_of_attempts = len(task_object.transactions)

        attempt_info = f'\nAttempt number: {number_of_attempts} ' \
                       f' for invocation round: {task_object.previous_invocations + 1}'

        if task_object.type == 'SEND_ETH':

            transfer_amount = int(task_object.amount)

            print(f'Starting Send Eth Transaction for {task_uuid}.' + attempt_info)
            chain1 = signature(utils.eth_endpoint('_process_send_eth_transaction'),
                          args=(transaction_obj.id,
                                task_object.recipient_address,
                                transfer_amount,
                                task_object.id))

        elif task_object.type == 'FUNCTION':
            print(f'Starting {task_object.function} Transaction for {task_uuid}.' + attempt_info)
            chain1 = signature(utils.eth_endpoint('_process_function_transaction'),
                               args=(transaction_obj.id,
                                     task_object.contract_address,
                                     task_object.abi_type,
                                     task_object.function,
                                     task_object.args,
                                     task_object.kwargs,
                                     task_object.gas_limit,
                                     task_object.id))

        elif task_object.type == 'DEPLOY_CONTRACT':
            print(f'Starting Deploy {task_object.contract_name} Contract Transaction for {task_uuid}.' + attempt_info)
            chain1 = signature(utils.eth_endpoint('_process_deploy_contract_transaction'),
                               args=(transaction_obj.id,
                                     task_object.contract_name,
                                     task_object.args,
                                     task_object.kwargs,
                                     task_object.gas_limit,
                                     task_object.id))
        else:
            raise Exception(f"Task type {task_object.type} not recognised")

        chain2 = signature(utils.eth_endpoint('_check_transaction_response'))

        error_callback = signature(utils.eth_endpoint('_log_error'), args=(transaction_obj.id,))

        return chain([chain1, chain2]).on_error(error_callback).delay()
Esempio n. 16
0
eth_config['gas_price_gwei'] = config.ETH_GAS_PRICE
eth_config['gas_limit'] = config.ETH_GAS_LIMIT

ETH_CHECK_TRANSACTION_RETRIES = config.ETH_CHECK_TRANSACTION_RETRIES
ETH_CHECK_TRANSACTION_RETRIES_TIME_LIMIT = config.ETH_CHECK_TRANSACTION_RETRIES_TIME_LIMIT
ETH_CHECK_TRANSACTION_BASE_TIME = config.ETH_CHECK_TRANSACTION_BASE_TIME


celery_app = Celery('tasks',
                    broker=config.REDIS_URL,
                    backend=config.REDIS_URL,
                    task_serializer='json')

celery_app.conf.beat_schedule = {
    "maintain_eth_balances": {
        "task": utils.eth_endpoint('topup_wallets'),
        "schedule": 600.0
    },
}

w3 = Web3(HTTPProvider(config.ETH_HTTP_PROVIDER))

red = redis.Redis.from_url(config.REDIS_URL)

persistence_interface = SQLPersistenceInterface(w3=w3, red=red)

blockchain_processor = TransactionProcessor(
    **eth_config,
    w3=w3,
    red=red,
    persistence_interface=persistence_interface