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