Skip to content

Commit

Permalink
Request user_id from PFS before direct transfer
Browse files Browse the repository at this point in the history
  • Loading branch information
karlb authored and ezdac committed Mar 29, 2021
1 parent b99220d commit 1e6bf9c
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 11 deletions.
31 changes: 31 additions & 0 deletions raiden/network/pathfinding.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import random
from dataclasses import dataclass
from datetime import datetime
from json import JSONDecodeError
from typing import Union
from urllib.parse import urlparse
from uuid import UUID

Expand Down Expand Up @@ -539,6 +541,35 @@ def post_pfs_paths(
)


def query_user(
pfs_config: PFSConfig,
user_address: Union[Address, TargetAddress],
) -> str:
""" Get the matrix user_id for the given address from the PFS """
try:
response = session.get(
f"{pfs_config.info.url}/api/v1/user/{to_checksum_address(user_address)}",
)
except RequestException as e:
raise ServiceRequestFailed(
f"Could not connect to Pathfinding Service ({str(e)})",
dict(exc_info=True),
)

try:
response_json = get_response_json(response)
except (ValueError, JSONDecodeError):
raise ServiceRequestFailed(
"Pathfinding Service returned malformed json in response",
{"http_code": response.status_code},
)

if response.status_code != 200:
raise PFSReturnedError.from_response(response_json)

return response_json["user_id"]


def query_paths(
pfs_config: PFSConfig,
our_address: Address,
Expand Down
10 changes: 5 additions & 5 deletions raiden/routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from raiden.exceptions import ServiceRequestFailed
from raiden.messages.metadata import RouteMetadata
from raiden.network.pathfinding import PFSConfig, query_paths
from raiden.network.pathfinding import PFSConfig, query_paths, query_user
from raiden.transfer import channel, views
from raiden.transfer.state import ChainState, ChannelState, RouteState
from raiden.utils.formatting import to_checksum_address
Expand Down Expand Up @@ -43,6 +43,10 @@ def get_best_routes(
token_network = views.get_token_network_by_address(chain_state, token_network_address)
assert token_network, "The token network must be validated and exist."

if pfs_config is None or one_to_n_address is None:
log.warning("Pathfinding Service could not be used.")
return "Pathfinding Service could not be used.", list(), None

# Always use a direct channel if available:
# - There are no race conditions and the capacity is guaranteed to be
# available.
Expand All @@ -69,10 +73,6 @@ def get_best_routes(
)
return None, [direct_route], None

if pfs_config is None or one_to_n_address is None:
log.warning("Pathfinding Service could not be used.")
return "Pathfinding Service could not be used.", list(), None

# Does any channel have sufficient capacity for the payment?
channels = [
token_network.channelidentifiers_to_channels[channel_id]
Expand Down
8 changes: 8 additions & 0 deletions raiden/tests/integration/fixtures/transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ def synapse_config_generator():
yield generator


@pytest.fixture(autouse=True)
def query_user_mock(local_matrix_servers, monkeypatch):
def query_user(_pfs_config, user_address):
return f"@0x{user_address.hex()}:{local_matrix_servers[0]}"

monkeypatch.setattr("raiden.routing.query_user", query_user)


@pytest.fixture
def matrix_server_count() -> int:
return 1
Expand Down
58 changes: 56 additions & 2 deletions raiden/tests/integration/network/test_pathfinding.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json
from unittest.mock import patch

import pytest
Expand All @@ -10,18 +11,27 @@
from requests.exceptions import RequestException

from raiden.constants import MATRIX_AUTO_SELECT_SERVER, RoutingMode
from raiden.exceptions import RaidenError
from raiden.exceptions import PFSReturnedError, RaidenError, ServiceRequestFailed
from raiden.network.pathfinding import (
PFSConfig,
PFSInfo,
check_pfs_for_production,
configure_pfs_or_exit,
query_user,
session,
)
from raiden.settings import DEFAULT_PATHFINDING_MAX_FEE
from raiden.tests.utils.factories import UNIT_CHAIN_ID, UNIT_OUR_ADDRESS, make_address
from raiden.tests.utils.mocks import mocked_json_response
from raiden.tests.utils.smartcontracts import deploy_service_registry_and_set_urls
from raiden.utils.keys import privatekey_to_address
from raiden.utils.typing import BlockNumber, ChainID, TokenAmount, TokenNetworkRegistryAddress
from raiden.utils.typing import (
BlockNumber,
BlockTimeout,
ChainID,
TokenAmount,
TokenNetworkRegistryAddress,
)

token_network_registry_address_test_default = TokenNetworkRegistryAddress(
to_canonical_address("0xB9633dd9a9a71F22C933bF121d7a22008f66B908")
Expand Down Expand Up @@ -184,3 +194,47 @@ def test_check_pfs_for_production(
)
with pytest.raises(RaidenError):
check_pfs_for_production(service_registry=service_registry, pfs_info=pfs_info)


def test_query_user():
matrix_user_id = "0x12345678901234567890@homeserver"
pfs_config = PFSConfig(
info=PFSInfo(
url="mock-address",
chain_id=UNIT_CHAIN_ID,
token_network_registry_address=make_address(),
user_deposit_address=make_address(),
payment_address=make_address(),
confirmed_block_number=BlockNumber(100),
message="",
operator="",
version="",
price=TokenAmount(0),
matrix_server="http://matrix.example.com",
),
maximum_fee=TokenAmount(100),
iou_timeout=BlockTimeout(100),
max_paths=5,
)

with patch("raiden.network.pathfinding.session") as session_mock:
# success
response = session_mock.get.return_value
response.status_code = 200
response.content = json.dumps({"user_id": matrix_user_id})
assert query_user(pfs_config, UNIT_OUR_ADDRESS) == matrix_user_id

# malformed response
response = session_mock.get.return_value
response.status_code = 200
response.content = "{wrong"
with pytest.raises(ServiceRequestFailed):
query_user(pfs_config, UNIT_OUR_ADDRESS)

# error response
response = session_mock.get.return_value
response.status_code = 400
response.content = json.dumps({"error_code": 123})
with pytest.raises(PFSReturnedError) as exc_info:
query_user(pfs_config, UNIT_OUR_ADDRESS)
assert exc_info.value["error_code"] == 123
11 changes: 7 additions & 4 deletions raiden/tests/unit/test_pfs_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -659,9 +659,11 @@ def test_routing_in_direct_channel(happy_path_fixture, our_address, one_to_n_add
address1, _, _, _ = addresses

# with the transfer of 50 the direct channel should be returned,
# so there must be not a pfs call
with patch("raiden.routing.get_best_routes_pfs") as pfs_request:
pfs_request.return_value = None, [], "feedback_token"
# so there must be not a route request to the pfs
with patch("raiden.routing.get_best_routes_pfs") as pfs_route_request, patch(
"raiden.routing.query_user"
) as pfs_user_request:
pfs_route_request.return_value = None, [], "feedback_token"
_, routes, _ = get_best_routes(
chain_state=chain_state,
token_network_address=token_network_state.address,
Expand All @@ -674,7 +676,8 @@ def test_routing_in_direct_channel(happy_path_fixture, our_address, one_to_n_add
privkey=PRIVKEY,
)
assert routes[0].next_hop_address == address1
assert not pfs_request.called
assert not pfs_route_request.called
assert pfs_user_request.called

# with the transfer of 51 the direct channel should not be returned,
# so there must be a pfs call
Expand Down

0 comments on commit 1e6bf9c

Please sign in to comment.