예제 #1
0
    def set_total_deposit(
        self,
        channel_identifier: typing.ChannelID,
        total_deposit: typing.TokenAmount,
        partner: typing.Address,
    ):
        """ Set total token deposit in the channel to total_deposit.

        Raises:
            ChannelBusyError: If the channel is busy with another operation
            RuntimeError: If the token address is empty.
        """
        if not isinstance(total_deposit, int):
            raise ValueError('total_deposit needs to be an integral number.')

        self._check_for_outdated_channel(
            self.node_address,
            partner,
            channel_identifier,
        )

        token_address = self.token_address()
        token = Token(self.client, token_address)

        with self.channel_operations_lock[partner], self.deposit_lock:
            # setTotalDeposit requires a monotonically increasing value. This
            # is used to handle concurrent actions:
            #
            #  - The deposits will be done in order, i.e. the monotonic
            #  property is preserved by the caller
            #  - The race of two deposits will be resolved with the larger
            #  deposit winning
            #  - Retries wont have effect
            #
            # This check is serialized with the channel_operations_lock to avoid
            # sending invalid transactions on-chain (decreasing total deposit).
            #
            current_deposit = self.detail_participant(
                channel_identifier,
                self.node_address,
                partner,
            ).deposit
            amount_to_deposit = total_deposit - current_deposit

            log_details = {
                'token_network': pex(self.address),
                'node': pex(self.node_address),
                'partner': pex(partner),
                'total_deposit': total_deposit,
                'amount_to_deposit': amount_to_deposit,
                'id': id(self),
            }
            log.debug('setTotalDeposit called', **log_details)

            # These two scenarions can happen if two calls to deposit happen
            # and then we get here on the second call
            if total_deposit < current_deposit:
                msg = (
                    f'Current deposit ({current_deposit}) is already larger '
                    f'than the requested total deposit amount ({total_deposit})'
                )
                log.info(f'setTotalDeposit failed, {msg}', **log_details)
                raise DepositMismatch(msg)

            if amount_to_deposit <= 0:
                msg = (f'total_deposit - current_deposit =  '
                       f'{amount_to_deposit} must be greater than 0.', )
                log.info(f'setTotalDeposit failed, {msg}', **log_details)
                raise DepositMismatch(msg)

            # A node may be setting up multiple channels for the same token
            # concurrently. Because each deposit changes the user balance this
            # check must be serialized with the operation locks.
            #
            # This check is merely informational, used to avoid sending
            # transactions which are known to fail.
            #
            # It is serialized with the deposit_lock to avoid sending invalid
            # transactions on-chain (account without balance). The lock
            # channel_operations_lock is not sufficient, as it allows two
            # concurrent deposits for different channels.
            #
            current_balance = token.balance_of(self.node_address)
            if current_balance < amount_to_deposit:
                msg = (
                    f'total_deposit - current_deposit =  {amount_to_deposit} can not '
                    f'be larger than the available balance {current_balance}, '
                    f'for token at address {pex(token_address)}')
                log.info(f'setTotalDeposit failed, {msg}', **log_details)
                raise DepositMismatch(msg)

            # If there are channels being set up concurrenlty either the
            # allowance must be accumulated *or* the calls to `approve` and
            # `setTotalDeposit` must be serialized. This is necessary otherwise
            # the deposit will fail.
            #
            # Calls to approve and setTotalDeposit are serialized with the
            # deposit_lock to avoid transaction failure, because with two
            # concurrent deposits, we may have the transactions executed in the
            # following order
            #
            # - approve
            # - approve
            # - setTotalDeposit
            # - setTotalDeposit
            #
            # in which case  the second `approve` will overwrite the first,
            # and the first `setTotalDeposit` will consume the allowance,
            #  making the second deposit fail.
            token.approve(self.address, amount_to_deposit)

            transaction_hash = self.proxy.transact(
                'setTotalDeposit',
                channel_identifier,
                self.node_address,
                total_deposit,
                partner,
            )
            self.client.poll(transaction_hash)

            receipt_or_none = check_transaction_threw(self.client,
                                                      transaction_hash)

            if receipt_or_none:
                if token.allowance(self.node_address,
                                   self.address) < amount_to_deposit:
                    log_msg = (
                        'setTotalDeposit failed. The allowance is insufficient, '
                        'check concurrent deposits for the same token network '
                        'but different proxies.')
                elif token.balance_of(self.node_address) < amount_to_deposit:
                    log_msg = 'setTotalDeposit failed. The address doesnt have funds'
                else:
                    log_msg = 'setTotalDeposit failed'

                log.critical(log_msg, **log_details)

                self._check_channel_state_for_deposit(
                    self.node_address,
                    partner,
                    channel_identifier,
                    total_deposit,
                )

                raise TransactionThrew('Deposit', receipt_or_none)

            log.info('setTotalDeposit successful', **log_details)
예제 #2
0
    def set_total_deposit(self, total_deposit: typing.TokenAmount, partner: typing.Address):
        """ Set total token deposit in the channel to total_deposit.

        Raises:
            ChannelBusyError: If the channel is busy with another operation
            RuntimeError: If the token address is empty.
        """
        if not isinstance(total_deposit, int):
            raise ValueError('total_deposit needs to be an integral number.')

        token_address = self.token_address()
        token = Token(self.client, token_address)

        with self.channel_operations_lock[partner], self.deposit_lock:
            # setTotalDeposit requires a monotonically increasing value. This
            # is used to handle concurrent actions:
            #
            #  - The deposits will be done in order, i.e. the monotonic
            #  property is preserved by the caller
            #  - The race of two deposits will be resolved with the larger
            #  deposit winning
            #  - Retries wont have effect
            #
            # This check is serialized with the channel_operations_lock to avoid
            # sending invalid transactions on-chain (decreasing total deposit).
            #
            current_deposit = self.detail_participant(self.node_address, partner)['deposit']
            amount_to_deposit = total_deposit - current_deposit
            if amount_to_deposit <= 0:
                raise ValueError(f'deposit {amount_to_deposit} must be greater than 0.')

            # A node may be setting up multiple channels for the same token
            # concurrently. Because each deposit changes the user balance this
            # check must be serialized with the operation locks.
            #
            # This check is merely informational, used to avoid sending
            # transactions which are known to fail.
            #
            # It is serialized with the deposit_lock to avoid sending invalid
            # transactions on-chain (account without balance). The lock
            # channel_operations_lock is not sufficient, as it allows two
            # concurrent deposits for different channels.
            #
            current_balance = token.balance_of(self.node_address)
            if current_balance < amount_to_deposit:
                raise ValueError(
                    f'deposit {amount_to_deposit} can not be larger than the '
                    f'available balance {current_balance}, '
                    f'for token at address {pex(token_address)}',
                )

            # If there are channels being set up concurrenlty either the
            # allowance must be accumulated *or* the calls to `approve` and
            # `setTotalDeposit` must be serialized. This is necessary otherwise
            # the deposit will fail.
            #
            # Calls to approve and setTotalDeposit are serialized with the
            # deposit_lock to avoid transaction failure, because with two
            # concurrent deposits, we may have the transactions executed in the
            # following order
            #
            # - approve
            # - approve
            # - setTotalDeposit
            # - setTotalDeposit
            #
            # in which case  the second `approve` will overwrite the first,
            # and the first `setTotalDeposit` will consume the allowance,
            #  making the second deposit fail.
            token.approve(self.address, amount_to_deposit)

            log_details = {
                'token_network': pex(self.address),
                'node': pex(self.node_address),
                'partner': pex(partner),
                'total_deposit': total_deposit,
                'amount_to_deposit': amount_to_deposit,
                'id': id(self),
            }
            log.info('deposit called', **log_details)

            transaction_hash = self.proxy.transact(
                'setTotalDeposit',
                self.node_address,
                total_deposit,
                partner,
            )
            self.client.poll(unhexlify(transaction_hash))

            receipt_or_none = check_transaction_threw(self.client, transaction_hash)

            if receipt_or_none:
                if token.allowance(self.node_address, self.address) < amount_to_deposit:
                    log_msg = (
                        'deposit failed. The allowance is insufficient, check concurrent deposits '
                        'for the same token network but different proxies.'
                    )
                elif token.balance_of(self.node_address) < amount_to_deposit:
                    log_msg = 'deposit failed. The address doesnt have funds'
                else:
                    log_msg = 'deposit failed'

                log.critical(log_msg, **log_details)

                channel_opened = self.channel_is_opened(self.node_address, partner)
                if channel_opened is False:
                    raise ChannelIncorrectStateError(
                        'Channel is not in an opened state. A deposit cannot be made',
                    )
                raise TransactionThrew('Deposit', receipt_or_none)

            log.info('deposit successful', **log_details)
예제 #3
0
    def set_total_deposit(self, total_deposit: typing.TokenAmount,
                          partner: typing.Address):
        """ Set total token deposit in the channel to total_deposit.

        Raises:
            ChannelBusyError: If the channel is busy with another operation
            RuntimeError: If the token address is empty.
        """
        if not isinstance(total_deposit, int):
            raise ValueError('total_deposit needs to be an integral number.')

        token_address = self.token_address()
        token = Token(self.client, token_address)

        self._check_channel_lock(partner)

        with releasing(
                self.channel_operations_lock[partner]), self.deposit_lock:

            # setTotalDeposit requires a monotonically increasing value. This
            # is used to handle concurrent actions:
            #
            #  - The deposits will be done in order, i.e. the monotonic
            #  property is preserved by the caller
            #  - The race of two deposits will be resolved with the larger
            #  deposit winning
            #  - Retries wont have effect
            #
            # This check is serialized with the channel_operations_lock to avoid
            # sending invalid transactions on-chain (decreasing total deposit).
            #
            current_deposit = self.detail_participant(self.node_address,
                                                      partner)['deposit']
            amount_to_deposit = total_deposit - current_deposit
            if amount_to_deposit <= 0:
                raise ValueError(
                    f'deposit {amount_to_deposit} must be greater than 0.')

            # A node may be setting up multiple channels for the same token
            # concurrently. Because each deposit changes the user balance this
            # check must be serialized with the operation locks.
            #
            # This check is merely informational, used to avoid sending
            # transactions which are known to fail.
            #
            # It is serialized with the channel_operations_lock to avoid
            # sending invalid transactions on-chain (account without balance).
            #
            current_balance = token.balance_of(self.node_address)
            if current_balance < amount_to_deposit:
                raise ValueError(
                    f'deposit {amount_to_deposit} can not be larger than the '
                    f'available balance {current_balance}, '
                    f'for token at address {pex(token_address)}', )

            # If there are channels being set up concurrenlty either the
            # allowance must be accumulated *or* the calls to `approve` and
            # `setTotalDeposit` must be serialized. This is necessary otherwise
            # the deposit will fail.
            #
            # Calls to approve and setTotalDeposit are serialized with the
            # channel_operations_lock to avoid transaction failure, because
            # with two concurrent deposits, we may have the transactions
            # executed in the following order
            #
            # - approve
            # - approve
            # - setTotalDeposit
            # - setTotalDeposit
            #
            # in which case  the second `approve` will overwrite the first,
            # and the first `setTotalDeposit` will consume the allowance,
            #  making the second deposit fail.
            token.approve(self.address, amount_to_deposit)

            log_details = {
                'token_network': pex(self.address),
                'node': pex(self.node_address),
                'partner': pex(partner),
                'total_deposit': total_deposit,
                'amount_to_deposit': amount_to_deposit,
                'id': id(self),
            }
            log.info('deposit called', **log_details)

            transaction_hash = self.proxy.transact(
                'setTotalDeposit',
                self.node_address,
                total_deposit,
                partner,
            )
            self.client.poll(unhexlify(transaction_hash))

            receipt_or_none = check_transaction_threw(self.client,
                                                      transaction_hash)

            if receipt_or_none:
                if token.allowance(self.node_address,
                                   self.address) != amount_to_deposit:
                    log_msg = (
                        'Deposit failed and allowance was consumed. Check concurrent deposits '
                        'for the same token network but different proxies.')
                else:
                    log_msg = 'deposit failed'

                log.critical(log_msg, **log_details)

                channel_opened = self.channel_is_opened(
                    self.node_address, partner)
                if channel_opened is False:
                    raise ChannelIncorrectStateError(
                        'Channel is not in an opened state. A deposit cannot be made',
                    )
                raise TransactionThrew('Deposit', receipt_or_none)

            log.info('deposit successful', **log_details)