def wait_for_usable_channel( raiden: RaidenService, partner_address: Address, token_network_registry_address: TokenNetworkRegistryAddress, token_address: TokenAddress, our_deposit: TokenAmount, partner_deposit: TokenAmount, retry_timeout: float = DEFAULT_RETRY_TIMEOUT, ) -> None: """ Wait until the channel from app0 to app1 is usable. The channel and the deposits are registered, and the partner network state is reachable. """ waiting.wait_for_newchannel( raiden=raiden, token_network_registry_address=token_network_registry_address, token_address=token_address, partner_address=partner_address, retry_timeout=retry_timeout, ) # wait for our deposit waiting.wait_for_participant_deposit( raiden=raiden, token_network_registry_address=token_network_registry_address, token_address=token_address, partner_address=partner_address, target_address=raiden.address, target_balance=our_deposit, retry_timeout=retry_timeout, ) # wait for the partner deposit waiting.wait_for_participant_deposit( raiden=raiden, token_network_registry_address=token_network_registry_address, token_address=token_address, partner_address=partner_address, target_address=partner_address, target_balance=partner_deposit, retry_timeout=retry_timeout, ) waiting.wait_for_healthy(raiden=raiden, node_address=partner_address, 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, )
def test_api_channel_open_and_deposit_race( api_server_test_instance: APIServer, raiden_network, token_addresses, reveal_timeout, token_network_registry_address, retry_timeout, ): """Tests that a race for the same deposit from the API is handled properly The proxy's approve_and_set_total_deposit is raising a RaidenRecoverableError in case of races. That needs to be properly handled and not allowed to bubble out of the greenlet. Regression test for https://github.com/raiden-network/raiden/issues/4937 """ app0 = raiden_network[0] # let's create a new channel first_partner_address = "0x61C808D82A3Ac53231750daDc13c777b59310bD9" token_address = token_addresses[0] settle_timeout = 1650 channel_data_obj = { "partner_address": first_partner_address, "token_address": to_checksum_address(token_address), "settle_timeout": str(settle_timeout), "reveal_timeout": str(reveal_timeout), } request = grequests.put(api_url_for(api_server_test_instance, "channelsresource"), json=channel_data_obj) response = request.send().response assert_proper_response(response, HTTPStatus.CREATED) json_response = get_json_response(response) expected_response = channel_data_obj.copy() expected_response.update({ "balance": "0", "state": ChannelState.STATE_OPENED.value, "channel_identifier": "1", "total_deposit": "0", }) assert check_dict_nested_attrs(json_response, expected_response) # Prepare the deposit api call deposit_amount = TokenAmount(99) request = grequests.patch( api_url_for( api_server_test_instance, "channelsresourcebytokenandpartneraddress", token_address=token_address, partner_address=first_partner_address, ), json={"total_deposit": str(deposit_amount)}, ) # Spawn two greenlets doing the same deposit request greenlets = [gevent.spawn(request.send), gevent.spawn(request.send)] gevent.joinall(set(greenlets), raise_error=True) # Make sure that both responses are fine g1_response = greenlets[0].get().response assert_proper_response(g1_response, HTTPStatus.OK) json_response = get_json_response(g1_response) expected_response.update({ "total_deposit": str(deposit_amount), "balance": str(deposit_amount) }) assert check_dict_nested_attrs(json_response, expected_response) g2_response = greenlets[0].get().response assert_proper_response(g2_response, HTTPStatus.OK) json_response = get_json_response(g2_response) assert check_dict_nested_attrs(json_response, expected_response) # Wait for the deposit to be seen timeout_seconds = 20 exception = Exception( f"Expected deposit not seen within {timeout_seconds}") with gevent.Timeout(seconds=timeout_seconds, exception=exception): wait_for_participant_deposit( raiden=app0.raiden, token_network_registry_address=token_network_registry_address, token_address=token_address, partner_address=to_canonical_address(first_partner_address), target_address=app0.raiden.address, target_balance=deposit_amount, retry_timeout=retry_timeout, ) request = grequests.get( api_url_for(api_server_test_instance, "channelsresource")) response = request.send().response assert_proper_response(response, HTTPStatus.OK) json_response = get_json_response(response) channel_info = json_response[0] assert channel_info["token_address"] == to_checksum_address(token_address) assert channel_info["total_deposit"] == str(deposit_amount)