Exemplo n.º 1
0
    def _deposit_preconditions(
        self,
        total_deposit: TokenAmount,
        beneficiary: Address,
        token: Token,
        block_identifier: BlockSpecification,
    ) -> Tuple[TokenAmount, Dict]:
        if not isinstance(total_deposit, int):
            raise ValueError('total_deposit needs to be an integer number.')

        previous_total_deposit = self.get_total_deposit(
            address=beneficiary,
            block_identifier=block_identifier,
        )
        amount_to_deposit = total_deposit - previous_total_deposit

        log_details = {
            'user_deposit_address': pex(self.address),
            'node': pex(self.node_address),
            'beneficiary': pex(beneficiary),
            'new_total_deposit': total_deposit,
            'previous_total_deposit': previous_total_deposit,
        }

        if total_deposit <= previous_total_deposit:
            msg = (
                f'Current total deposit {previous_total_deposit} is already larger '
                f'than the requested total deposit amount {total_deposit}')
            log.info('deposit failed', reason=msg, **log_details)
            raise DepositMismatch(msg)

        current_balance = token.balance_of(
            address=self.node_address,
            block_identifier=block_identifier,
        )
        if current_balance < amount_to_deposit:
            msg = (
                f'new_total_deposit - previous_total_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('deposit failed', reason=msg, **log_details)
            raise DepositMismatch(msg)

        token.approve(
            allowed_address=Address(self.address),
            allowance=amount_to_deposit,
        )

        return amount_to_deposit, log_details
Exemplo n.º 2
0
    def _deposit_preconditions(
        self,
        total_deposit: TokenAmount,
        beneficiary: Address,
        token: Token,
        block_identifier: BlockSpecification,
    ) -> Tuple[TokenAmount, Dict]:
        if not isinstance(total_deposit, int):
            raise ValueError("total_deposit needs to be an integer number.")

        previous_total_deposit = self.get_total_deposit(
            address=beneficiary, block_identifier=block_identifier
        )
        amount_to_deposit = TokenAmount(total_deposit - previous_total_deposit)

        log_details = {
            "user_deposit_address": pex(self.address),
            "node": pex(self.node_address),
            "beneficiary": pex(beneficiary),
            "new_total_deposit": total_deposit,
            "previous_total_deposit": previous_total_deposit,
        }

        if total_deposit <= previous_total_deposit:
            msg = (
                f"Current total deposit {previous_total_deposit} is already larger "
                f"than the requested total deposit amount {total_deposit}"
            )
            log.info("deposit failed", reason=msg, **log_details)
            raise DepositMismatch(msg)

        current_balance = token.balance_of(
            address=self.node_address, block_identifier=block_identifier
        )
        if current_balance < amount_to_deposit:
            msg = (
                f"new_total_deposit - previous_total_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("deposit failed", reason=msg, **log_details)
            raise DepositMismatch(msg)

        token.approve(allowed_address=Address(self.address), allowance=amount_to_deposit)

        return amount_to_deposit, log_details
Exemplo n.º 3
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)
Exemplo n.º 4
0
    def set_total_channel_deposit(
            self,
            registry_address: typing.PaymentNetworkID,
            token_address: typing.TokenAddress,
            partner_address: typing.Address,
            total_deposit: typing.TokenAmount,
            retry_timeout: typing.NetworkTimeout = DEFAULT_RETRY_TIMEOUT,
    ):
        """ Set the `total_deposit` in the channel with the peer at `partner_address` and the
        given `token_address` in order to be able to do transfers.

        Raises:
            InvalidAddress: If either token_address or partner_address is not
                20 bytes long.
                TransactionThrew: May happen for multiple reasons:
                - If the token approval fails, e.g. the token may validate if
                account has enough balance for the allowance.
                - The deposit failed, e.g. the allowance did not set the token
                aside for use and the user spent it before deposit was called.
                - The channel was closed/settled between the allowance call and
                the deposit call.
            AddressWithoutCode: The channel was settled during the deposit
                execution.
            DepositOverLimit: The total deposit amount is higher than the limit.
        """
        chain_state = views.state_from_raiden(self.raiden)

        token_networks = views.get_token_network_addresses_for(
            chain_state,
            registry_address,
        )
        channel_state = views.get_channelstate_for(
            chain_state=chain_state,
            payment_network_id=registry_address,
            token_address=token_address,
            partner_address=partner_address,
        )

        if not is_binary_address(token_address):
            raise InvalidAddress('Expected binary address format for token in channel deposit')

        if not is_binary_address(partner_address):
            raise InvalidAddress('Expected binary address format for partner in channel deposit')

        if token_address not in token_networks:
            raise UnknownTokenAddress('Unknown token address')

        if channel_state is None:
            raise InvalidAddress('No channel with partner_address for the given token')

        token = self.raiden.chain.token(token_address)
        token_network_registry = self.raiden.chain.token_network_registry(registry_address)
        token_network_address = token_network_registry.get_token_network(token_address)
        token_network_proxy = self.raiden.chain.token_network(token_network_address)
        channel_proxy = self.raiden.chain.payment_channel(
            token_network_address=token_network_proxy.address,
            channel_id=channel_state.identifier,
        )

        balance = token.balance_of(self.raiden.address)

        if self.raiden.config['environment_type'] == Environment.PRODUCTION:
            deposit_limit = (
                token_network_proxy.proxy.contract.functions.
                channel_participant_deposit_limit().call()
            )
        elif self.raiden.config['environment_type'] == Environment.DEVELOPMENT:
            deposit_limit = (
                token_network_proxy.proxy.contract.functions.
                deposit_limit().call()
            )

        if total_deposit > deposit_limit:
            raise DepositOverLimit(
                'The deposit of {} is bigger than the current limit of {}'.format(
                    total_deposit,
                    deposit_limit,
                ),
            )

        if total_deposit == 0:
            raise DepositMismatch('Attempted to deposit with total deposit being 0')

        addendum = total_deposit - channel_state.our_state.contract_balance

        # If this check succeeds it does not imply the the `deposit` will
        # succeed, since the `deposit` transaction may race with another
        # transaction.
        if not balance >= addendum:
            msg = 'Not enough balance to deposit. {} Available={} Needed={}'.format(
                pex(token_address),
                balance,
                addendum,
            )
            raise InsufficientFunds(msg)

        # set_total_deposit calls approve
        # token.approve(netcontract_address, addendum)
        channel_proxy.set_total_deposit(total_deposit)

        target_address = self.raiden.address
        waiting.wait_for_participant_newbalance(
            raiden=self.raiden,
            payment_network_id=registry_address,
            token_address=token_address,
            partner_address=partner_address,
            target_address=target_address,
            target_balance=total_deposit,
            retry_timeout=retry_timeout,
        )
Exemplo n.º 5
0
    def set_total_channel_deposit(
        self,
        registry_address: PaymentNetworkID,
        token_address: TokenAddress,
        partner_address: Address,
        total_deposit: TokenAmount,
        retry_timeout: NetworkTimeout = DEFAULT_RETRY_TIMEOUT,
    ):
        """ Set the `total_deposit` in the channel with the peer at `partner_address` and the
        given `token_address` in order to be able to do transfers.

        Raises:
            InvalidAddress: If either token_address or partner_address is not
                20 bytes long.
            TransactionThrew: May happen for multiple reasons:
                - If the token approval fails, e.g. the token may validate if
                account has enough balance for the allowance.
                - The deposit failed, e.g. the allowance did not set the token
                aside for use and the user spent it before deposit was called.
                - The channel was closed/settled between the allowance call and
                the deposit call.
            AddressWithoutCode: The channel was settled during the deposit
                execution.
            DepositOverLimit: The total deposit amount is higher than the limit.
        """
        chain_state = views.state_from_raiden(self.raiden)

        token_addresses = views.get_token_identifiers(chain_state, registry_address)
        channel_state = views.get_channelstate_for(
            chain_state=chain_state,
            payment_network_id=registry_address,
            token_address=token_address,
            partner_address=partner_address,
        )

        if not is_binary_address(token_address):
            raise InvalidAddress("Expected binary address format for token in channel deposit")

        if not is_binary_address(partner_address):
            raise InvalidAddress("Expected binary address format for partner in channel deposit")

        if token_address not in token_addresses:
            raise UnknownTokenAddress("Unknown token address")

        if channel_state is None:
            raise InvalidAddress("No channel with partner_address for the given token")

        if self.raiden.config["environment_type"] == Environment.PRODUCTION:
            per_token_network_deposit_limit = RED_EYES_PER_TOKEN_NETWORK_LIMIT
        else:
            per_token_network_deposit_limit = UINT256_MAX

        token = self.raiden.chain.token(token_address)
        token_network_registry = self.raiden.chain.token_network_registry(registry_address)
        token_network_address = token_network_registry.get_token_network(token_address)
        token_network_proxy = self.raiden.chain.token_network(token_network_address)
        channel_proxy = self.raiden.chain.payment_channel(
            canonical_identifier=channel_state.canonical_identifier
        )

        if total_deposit == 0:
            raise DepositMismatch("Attempted to deposit with total deposit being 0")

        addendum = total_deposit - channel_state.our_state.contract_balance

        total_network_balance = token.balance_of(registry_address)

        if total_network_balance + addendum > per_token_network_deposit_limit:
            raise DepositOverLimit(
                f"The deposit of {addendum} will exceed the "
                f"token network limit of {per_token_network_deposit_limit}"
            )

        balance = token.balance_of(self.raiden.address)

        functions = token_network_proxy.proxy.contract.functions
        deposit_limit = functions.channel_participant_deposit_limit().call()

        if total_deposit > deposit_limit:
            raise DepositOverLimit(
                f"The additional deposit of {addendum} will exceed the "
                f"channel participant limit of {deposit_limit}"
            )

        # If this check succeeds it does not imply the the `deposit` will
        # succeed, since the `deposit` transaction may race with another
        # transaction.
        if not balance >= addendum:
            msg = "Not enough balance to deposit. {} Available={} Needed={}".format(
                pex(token_address), balance, addendum
            )
            raise InsufficientFunds(msg)

        # set_total_deposit calls approve
        # token.approve(netcontract_address, addendum)
        channel_proxy.set_total_deposit(
            total_deposit=total_deposit,
            block_identifier=views.state_from_raiden(self.raiden).block_hash,
        )

        target_address = self.raiden.address
        waiting.wait_for_participant_newbalance(
            raiden=self.raiden,
            payment_network_id=registry_address,
            token_address=token_address,
            partner_address=partner_address,
            target_address=target_address,
            target_balance=total_deposit,
            retry_timeout=retry_timeout,
        )
Exemplo n.º 6
0
    def set_total_channel_deposit(
        self,
        registry_address: TokenNetworkRegistryAddress,
        token_address: TokenAddress,
        partner_address: Address,
        total_deposit: TokenAmount,
        retry_timeout: NetworkTimeout = DEFAULT_RETRY_TIMEOUT,
    ) -> None:
        """ Set the `total_deposit` in the channel with the peer at `partner_address` and the
        given `token_address` in order to be able to do transfers.

        Raises:
            InvalidBinaryAddress: If either token_address or partner_address is not
                20 bytes long.
            RaidenRecoverableError: May happen for multiple reasons:
                - If the token approval fails, e.g. the token may validate if
                account has enough balance for the allowance.
                - The deposit failed, e.g. the allowance did not set the token
                aside for use and the user spent it before deposit was called.
                - The channel was closed/settled between the allowance call and
                the deposit call.
            AddressWithoutCode: The channel was settled during the deposit
                execution.
            DepositOverLimit: The total deposit amount is higher than the limit.
            UnexpectedChannelState: The channel is no longer in an open state.
        """
        chain_state = views.state_from_raiden(self.raiden)

        token_addresses = views.get_token_identifiers(chain_state,
                                                      registry_address)
        channel_state = views.get_channelstate_for(
            chain_state=chain_state,
            token_network_registry_address=registry_address,
            token_address=token_address,
            partner_address=partner_address,
        )

        if not is_binary_address(token_address):
            raise InvalidBinaryAddress(
                "Expected binary address format for token in channel deposit")

        if not is_binary_address(partner_address):
            raise InvalidBinaryAddress(
                "Expected binary address format for partner in channel deposit"
            )

        if token_address not in token_addresses:
            raise UnknownTokenAddress("Unknown token address")

        if channel_state is None:
            raise NonexistingChannel(
                "No channel with partner_address for the given token")

        confirmed_block_identifier = chain_state.block_hash
        token = self.raiden.proxy_manager.token(
            token_address, block_identifier=confirmed_block_identifier)
        token_network_registry = self.raiden.proxy_manager.token_network_registry(
            registry_address, block_identifier=confirmed_block_identifier)
        token_network_address = token_network_registry.get_token_network(
            token_address=token_address,
            block_identifier=confirmed_block_identifier)

        if token_network_address is None:
            raise UnknownTokenAddress(
                f"Token {to_checksum_address(token_address)} is not registered "
                f"with the network {to_checksum_address(registry_address)}.")

        token_network_proxy = self.raiden.proxy_manager.token_network(
            address=token_network_address,
            block_identifier=confirmed_block_identifier)
        channel_proxy = self.raiden.proxy_manager.payment_channel(
            channel_state=channel_state,
            block_identifier=confirmed_block_identifier)

        blockhash = chain_state.block_hash
        token_network_proxy = channel_proxy.token_network

        safety_deprecation_switch = token_network_proxy.safety_deprecation_switch(
            block_identifier=blockhash)

        balance = token.balance_of(self.raiden.address,
                                   block_identifier=blockhash)

        network_balance = token.balance_of(
            address=Address(token_network_address), block_identifier=blockhash)
        token_network_deposit_limit = token_network_proxy.token_network_deposit_limit(
            block_identifier=blockhash)

        addendum = total_deposit - channel_state.our_state.contract_balance

        channel_participant_deposit_limit = token_network_proxy.channel_participant_deposit_limit(
            block_identifier=blockhash)
        total_channel_deposit = total_deposit + channel_state.partner_state.contract_balance

        is_channel_open = channel.get_status(
            channel_state) == ChannelState.STATE_OPENED

        if not is_channel_open:
            raise UnexpectedChannelState("Channel is not in an open state.")

        if safety_deprecation_switch:
            msg = ("This token_network has been deprecated. "
                   "All channels in this network should be closed and "
                   "the usage of the newly deployed token network contract "
                   "is highly encouraged.")
            raise TokenNetworkDeprecated(msg)

        if total_deposit <= channel_state.our_state.contract_balance:
            raise DepositMismatch("Total deposit did not increase.")

        # If this check succeeds it does not imply the `deposit` will
        # succeed, since the `deposit` transaction may race with another
        # transaction.
        if not (balance >= addendum):
            msg = "Not enough balance to deposit. {} Available={} Needed={}".format(
                to_checksum_address(token_address), balance, addendum)
            raise InsufficientFunds(msg)

        if network_balance + addendum > token_network_deposit_limit:
            msg = f"Deposit of {addendum} would have exceeded the token network deposit limit."
            raise DepositOverLimit(msg)

        if total_deposit > channel_participant_deposit_limit:
            msg = (f"Deposit of {total_deposit} is larger than the "
                   f"channel participant deposit limit")
            raise DepositOverLimit(msg)

        if total_channel_deposit >= UINT256_MAX:
            raise DepositOverLimit("Deposit overflow")

        try:
            channel_proxy.approve_and_set_total_deposit(
                total_deposit=total_deposit, block_identifier=blockhash)
        except RaidenRecoverableError as e:
            log.info(f"Deposit failed. {str(e)}")

        target_address = self.raiden.address
        waiting.wait_for_participant_deposit(
            raiden=self.raiden,
            token_network_registry_address=registry_address,
            token_address=token_address,
            partner_address=partner_address,
            target_address=target_address,
            target_balance=total_deposit,
            retry_timeout=retry_timeout,
        )