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
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
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)
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, )
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, )
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, )