def withdraw( self, partner: typing.Address, total_withdraw: int, partner_signature: typing.Address, signature: typing.Signature, ): log.info( 'withdraw called', token_network=pex(self.address), node=pex(self.node_address), partner=pex(partner), total_withdraw=total_withdraw, ) self._check_channel_lock(partner) with releasing(self.channel_operations_lock[partner]): transaction_hash = self.proxy.transact( 'setTotalWithdraw', self.node_address, partner, total_withdraw, partner_signature, signature, ) self.client.poll(unhexlify(transaction_hash), timeout=self.poll_timeout) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.critical( 'withdraw failed', token_network=pex(self.address), node=pex(self.node_address), partner=pex(partner), total_withdraw=total_withdraw, partner_signature=encode_hex(partner_signature), signature=encode_hex(signature), ) channel_opened = self.channel_is_opened(partner) if channel_opened is False: raise ChannelIncorrectStateError( 'Channel is not in an opened state. A withdraw cannot be made', ) raise TransactionThrew('Withdraw', receipt_or_none) log.info( 'withdraw successful', token_network=pex(self.address), node=pex(self.node_address), partner=pex(partner), total_withdraw=total_withdraw, partner_signature=encode_hex(partner_signature), signature=encode_hex(signature), )
def set_total_deposit(self, total_deposit): """ Set the total deposit of token in the channel. Raises: AddressWithoutCode: If the channel was settled prior to the call. ChannelBusyError: If the channel is busy with another operation RuntimeError: If the netting channel token address is empty. """ if not isinstance(total_deposit, int): raise ValueError('total_deposit needs to be an integral number.') log.info( 'set_total_deposit called', node=pex(self.node_address), contract=pex(self.address), amount=total_deposit, ) if not self.channel_operations_lock.acquire(blocking=False): raise ChannelBusyError( f'Channel with address {self.address} is ' f'busy with another ongoing operation.', ) with releasing(self.channel_operations_lock): transaction_hash = self.proxy.transact( 'setTotalDeposit', total_deposit, ) self.client.poll( unhexlify(transaction_hash), timeout=self.poll_timeout, ) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.critical( 'set_total_deposit failed', node=pex(self.node_address), contract=pex(self.address), ) self._check_exists() raise TransactionThrew('Deposit', receipt_or_none) log.info( 'set_total_deposit successful', node=pex(self.node_address), contract=pex(self.address), amount=total_deposit, )
def settle(self): """ Settle the channel. Raises: ChannelBusyError: If the channel is busy with another operation """ if log.isEnabledFor(logging.INFO): log.info( 'settle called', node=pex(self.node_address), ) if not self.channel_operations_lock.acquire(blocking=False): raise ChannelBusyError(f'Channel with address {self.address} is ' f'busy with another ongoing operation') with releasing(self.channel_operations_lock): transaction_hash = estimate_and_transact( self.proxy, 'settle', ) self.client.poll(unhexlify(transaction_hash), timeout=self.poll_timeout) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.info( 'settle failed', node=pex(self.node_address), contract=pex(self.address), ) self._check_exists() raise TransactionThrew('Settle', receipt_or_none) if log.isEnabledFor(logging.INFO): log.info( 'settle successful', node=pex(self.node_address), contract=pex(self.address), )
def close(self, nonce, transferred_amount, locked_amount, locksroot, extra_hash, signature): """ Close the channel using the provided balance proof. Raises: AddressWithoutCode: If the channel was settled prior to the call. ChannelBusyError: If the channel is busy with another operation. """ log.info( 'close called', node=pex(self.node_address), contract=pex(self.address), nonce=nonce, transferred_amount=transferred_amount, locked_amount=locked_amount, locksroot=encode_hex(locksroot), extra_hash=encode_hex(extra_hash), signature=encode_hex(signature), ) if not self.channel_operations_lock.acquire(blocking=False): raise ChannelBusyError( f'Channel with address {self.address} is ' f'busy with another ongoing operation.', ) with releasing(self.channel_operations_lock): transaction_hash = self.proxy.transact( 'close', nonce, transferred_amount, locked_amount, locksroot, extra_hash, signature, ) self.client.poll(unhexlify(transaction_hash), timeout=self.poll_timeout) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.critical( 'close failed', node=pex(self.node_address), contract=pex(self.address), nonce=nonce, transferred_amount=transferred_amount, locked_amount=locked_amount, locksroot=encode_hex(locksroot), extra_hash=encode_hex(extra_hash), signature=encode_hex(signature), ) self._check_exists() raise TransactionThrew('Close', receipt_or_none) log.info( 'close successful', node=pex(self.node_address), contract=pex(self.address), nonce=nonce, transferred_amount=transferred_amount, locked_amount=locked_amount, locksroot=encode_hex(locksroot), extra_hash=encode_hex(extra_hash), signature=encode_hex(signature), )
def channel_deposit(self, token_address, partner_address, amount, poll_timeout=DEFAULT_POLL_TIMEOUT): """ Deposit `amount` 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. """ node_state = views.state_from_raiden(self.raiden) registry_address = self.raiden.default_registry.address token_networks = views.get_token_network_addresses_for( node_state, registry_address, ) channel_state = views.get_channelstate_for( node_state, registry_address, token_address, partner_address, ) if not isaddress(token_address): raise InvalidAddress( 'Expected binary address format for token in channel deposit') if not isaddress(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) balance = token.balance_of(hexlify(self.raiden.address)) # 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 >= amount: msg = 'Not enough balance to deposit. {} Available={} Tried={}'.format( pex(token_address), balance, amount, ) raise InsufficientFunds(msg) netcontract_address = channel_state.identifier channel_proxy = self.raiden.chain.netting_channel(netcontract_address) # If concurrent operations are happening on the channel, fail the request if not channel_proxy.channel_operations_lock.acquire(blocking=False): raise ChannelBusyError( f'Channel with id {channel_state.identifier} is ' f'busy with another ongoing operation') with releasing(channel_proxy.channel_operations_lock): token.approve(netcontract_address, amount) channel_proxy.deposit(amount) old_balance = channel_state.our_state.contract_balance target_balance = old_balance + amount msg = 'After {} seconds the deposit was not properly processed.'.format( poll_timeout) # Wait until the `ChannelNewBalance` event is processed. with gevent.Timeout(poll_timeout, EthNodeCommunicationError(msg)): waiting.wait_for_newbalance( self.raiden, registry_address, token_address, partner_address, target_balance, self.raiden.alarm.wait_time, )
def settle( self, transferred_amount: int, locked_amount: int, locksroot: typing.Locksroot, partner: typing.Address, partner_transferred_amount: int, partner_locked_amount: int, partner_locksroot: typing.Locksroot, ): """ Settle the channel. Raises: ChannelBusyError: If the channel is busy with another operation """ log.info( 'settle called', token_network=pex(self.address), node=pex(self.node_address), partner=pex(partner), transferred_amount=transferred_amount, locked_amount=locked_amount, locksroot=encode_hex(locksroot), partner_transferred_amount=partner_transferred_amount, partner_locked_amount=partner_locked_amount, partner_locksroot=encode_hex(partner_locksroot), ) self._check_channel_lock(partner) with releasing(self.channel_operations_lock[partner]): transaction_hash = self.proxy.transact( 'settleChannel', self.node_address, transferred_amount, locked_amount, locksroot, partner, partner_transferred_amount, partner_locked_amount, partner_locksroot, ) self.client.poll(unhexlify(transaction_hash), timeout=self.poll_timeout) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.info( 'settle failed', token_network=pex(self.address), node=pex(self.node_address), partner=pex(partner), transferred_amount=transferred_amount, locked_amount=locked_amount, locksroot=encode_hex(locksroot), partner_transferred_amount=partner_transferred_amount, partner_locked_amount=partner_locked_amount, partner_locksroot=encode_hex(partner_locksroot), ) channel_closed = self.channel_is_closed(partner) if channel_closed is False: raise ChannelIncorrectStateError( 'Channel is not in a closed state. It cannot be settled', ) raise TransactionThrew('Settle', receipt_or_none) log.info( 'settle successful', token_network=pex(self.address), node=pex(self.node_address), partner=pex(partner), transferred_amount=transferred_amount, locked_amount=locked_amount, locksroot=encode_hex(locksroot), partner_transferred_amount=partner_transferred_amount, partner_locked_amount=partner_locked_amount, partner_locksroot=encode_hex(partner_locksroot), )
def close( self, partner: typing.Address, nonce: typing.Nonce, balance_hash: typing.BalanceHash, additional_hash: typing.AdditionalHash, signature: typing.Signature, ): """ Close the channel using the provided balance proof. Raises: ChannelBusyError: If the channel is busy with another operation. """ log.info( 'close called', token_network=pex(self.address), node=pex(self.node_address), partner=pex(partner), nonce=nonce, balance_hash=encode_hex(balance_hash), additional_hash=encode_hex(additional_hash), signature=encode_hex(signature), ) self._check_channel_lock(partner) with releasing(self.channel_operations_lock[partner]): transaction_hash = self.proxy.transact( 'closeChannel', partner, balance_hash, nonce, additional_hash, signature, ) self.client.poll(unhexlify(transaction_hash), timeout=self.poll_timeout) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.critical( 'close failed', token_network=pex(self.address), node=pex(self.node_address), partner=pex(partner), nonce=nonce, balance_hash=encode_hex(balance_hash), additional_hash=encode_hex(additional_hash), signature=encode_hex(signature), ) channel_opened = self.channel_is_opened(partner) if channel_opened is False: raise ChannelIncorrectStateError( 'Channel is not in an opened state. It cannot be closed.', ) raise TransactionThrew('Close', receipt_or_none) log.info( 'close successful', token_network=pex(self.address), node=pex(self.node_address), partner=pex(partner), nonce=nonce, balance_hash=encode_hex(balance_hash), additional_hash=encode_hex(additional_hash), signature=encode_hex(signature), )
def 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.poll_timeout, ) current_balance = token.balance_of(self.node_address) current_deposit = self.detail_participant(self.node_address, partner)['deposit'] amount_to_deposit = total_deposit - current_deposit if amount_to_deposit <= 0: raise ValueError('deposit [{}] must be greater than 0.'.format( amount_to_deposit, )) if current_balance < amount_to_deposit: raise ValueError( f'deposit {amount_to_deposit} cant be larger than the ' f'available balance {current_balance}, ' f'for token at address {pex(token_address)}', ) log.info( 'deposit called', token_network=pex(self.address), node=pex(self.node_address), partner=pex(partner), total_deposit=total_deposit, amount_to_deposit=amount_to_deposit, ) self._check_channel_lock(partner) with releasing(self.channel_operations_lock[partner]): transaction_hash = self.proxy.transact( 'setTotalDeposit', self.node_address, total_deposit, partner, ) self.client.poll( unhexlify(transaction_hash), timeout=self.poll_timeout, ) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.critical( 'deposit failed', token_network=pex(self.address), node=pex(self.node_address), partner=pex(partner), total_deposit=total_deposit, ) channel_opened = self.channel_is_opened(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', token_network=pex(self.address), node=pex(self.node_address), partner=pex(partner), total_deposit=total_deposit, )
def deposit(self, amount): """ Deposit amount token in the channel. Raises: AddressWithoutCode: If the channel was settled prior to the call. ChannelBusyError: If the channel is busy with another operation RuntimeError: If the netting channel token address is empty. """ if not isinstance(amount, int): raise ValueError('amount needs to be an integral number.') token_address = self.token_address() token = Token( self.client, token_address, self.poll_timeout, ) current_balance = token.balance_of(self.node_address) if current_balance < amount: raise ValueError('deposit [{}] cant be larger than the available balance [{}].'.format( amount, current_balance, )) log.info( 'deposit called', node=pex(self.node_address), contract=pex(self.address), amount=amount, ) if not self.channel_operations_lock.acquire(blocking=False): raise ChannelBusyError( f'Channel with address {self.address} is ' f'busy with another ongoing operation.' ) with releasing(self.channel_operations_lock): transaction_hash = estimate_and_transact( self.proxy, 'deposit', amount, ) self.client.poll( unhexlify(transaction_hash), timeout=self.poll_timeout, ) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.critical( 'deposit failed', node=pex(self.node_address), contract=pex(self.address), ) self._check_exists() raise TransactionThrew('Deposit', receipt_or_none) log.info( 'deposit successful', node=pex(self.node_address), contract=pex(self.address), amount=amount, )
def set_total_channel_deposit( self, registry_address, token_address, partner_address, total_deposit, poll_timeout=DEFAULT_POLL_TIMEOUT, retry_timeout=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. """ node_state = views.state_from_raiden(self.raiden) token_networks = views.get_token_network_addresses_for( node_state, registry_address, ) channel_state = views.get_channelstate_for( node_state, registry_address, token_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) netcontract_address = channel_state.identifier token_network_registry = self.raiden.chain.token_network_registry( registry_address) token_network_proxy = token_network_registry.token_network_by_token( token_address) channel_proxy = self.raiden.chain.payment_channel( token_network_proxy.address, netcontract_address, ) balance = token.balance_of(self.raiden.address) 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 <= channel_state.our_state.contract_balance: # no action required return 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) # If concurrent operations are happening on the channel, fail the request if not channel_proxy.channel_operations_lock.acquire(blocking=False): raise ChannelBusyError( f'Channel with id {channel_state.identifier} is ' f'busy with another ongoing operation', ) with releasing(channel_proxy.channel_operations_lock): # set_total_deposit calls approve # token.approve(netcontract_address, addendum) channel_proxy.set_total_deposit(total_deposit) msg = 'After {} seconds the deposit was not properly processed.'.format( poll_timeout, ) # Wait until the `ChannelNewBalance` event is processed. with gevent.Timeout(poll_timeout, EthNodeCommunicationError(msg)): target_address = self.raiden.address waiting.wait_for_participant_newbalance( self.raiden, registry_address, token_address, partner_address, target_address, total_deposit, retry_timeout, )
def settle( self, transferred_amount: int, locked_amount: int, locksroot: typing.Locksroot, partner: typing.Address, partner_transferred_amount: int, partner_locked_amount: int, partner_locksroot: typing.Locksroot, ): """ Settle the channel. Raises: ChannelBusyError: If the channel is busy with another operation """ log.info( 'settle called', token_network=pex(self.address), node=pex(self.node_address), partner=pex(partner), transferred_amount=transferred_amount, locked_amount=locked_amount, locksroot=encode_hex(locksroot), partner_transferred_amount=partner_transferred_amount, partner_locked_amount=partner_locked_amount, partner_locksroot=encode_hex(partner_locksroot), ) self._check_channel_lock(partner) with releasing(self.channel_operations_lock[partner]): # the second participants transferred + locked amount have to be higher than the first if (transferred_amount + locked_amount > partner_transferred_amount + partner_locked_amount): transaction_hash = self.proxy.transact( 'settleChannel', partner, partner_transferred_amount, partner_locked_amount, partner_locksroot, self.node_address, transferred_amount, locked_amount, locksroot, ) else: transaction_hash = self.proxy.transact( 'settleChannel', self.node_address, transferred_amount, locked_amount, locksroot, partner, partner_transferred_amount, partner_locked_amount, partner_locksroot, ) self.client.poll(unhexlify(transaction_hash)) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: log.info( 'settle failed', token_network=pex(self.address), node=pex(self.node_address), partner=pex(partner), transferred_amount=transferred_amount, locked_amount=locked_amount, locksroot=encode_hex(locksroot), partner_transferred_amount=partner_transferred_amount, partner_locked_amount=partner_locked_amount, partner_locksroot=encode_hex(partner_locksroot), ) channel_exists = self.channel_exists(self.node_address, partner) if not channel_exists: raise ChannelIncorrectStateError( 'Channel already settled or non-existent', ) channel_closed = self.channel_is_closed(self.node_address, partner) if channel_closed is False: raise ChannelIncorrectStateError( 'Channel is not in a closed state. It cannot be settled', ) raise TransactionThrew('Settle', receipt_or_none) log.info( 'settle successful', token_network=pex(self.address), node=pex(self.node_address), partner=pex(partner), transferred_amount=transferred_amount, locked_amount=locked_amount, locksroot=encode_hex(locksroot), partner_transferred_amount=partner_transferred_amount, partner_locked_amount=partner_locked_amount, partner_locksroot=encode_hex(partner_locksroot), )
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)