def test_get_pfs_iou(one_to_n_address): token_network_address = TokenNetworkAddress(bytes([1] * 20)) privkey = bytes([2] * 32) sender = privatekey_to_address(privkey) receiver = factories.make_address() response = mocked_json_response(response_data={"last_iou": None}) with patch.object(session, "get", return_value=response): assert (get_last_iou("http://example.com", token_network_address, sender, receiver, PRIVKEY) is None) # Previous IOU iou = IOU( sender=sender, receiver=receiver, amount=10, expiration_block=1000, one_to_n_address=one_to_n_address, chain_id=4, ) iou.sign(privkey) response = mocked_json_response(response_data={"last_iou": iou.as_json()}) with patch.object(session, "get", return_value=response): assert (get_last_iou("http://example.com", token_network_address, sender, receiver, PRIVKEY) == iou)
def assert_failed_pfs_request( paths_args: typing.Dict[str, typing.Any], responses: typing.List[typing.Dict], status_codes: typing.Sequence[int] = (400, 400), expected_requests: int = MAX_PATHS_QUERY_ATTEMPTS, expected_get_iou_requests: int = None, expected_success: bool = False, exception_type: typing.Type = ServiceRequestFailed, ): while len(responses) < MAX_PATHS_QUERY_ATTEMPTS: responses.append(responses[0]) for response in responses: if "error_code" in response: response["errors"] = "broken iou" path_mocks = [ mocked_json_response(response_data=data, status_code=status_code) for data, status_code in zip(responses, status_codes) ] with patch("raiden.network.pathfinding.get_pfs_info") as mocked_pfs_info: mocked_pfs_info.return_value = PFS_CONFIG.info with patch.object(session, "get", return_value=mocked_json_response()) as get_iou: with patch.object(session, "post", side_effect=path_mocks) as post_paths: if expected_success: query_paths(**paths_args) else: with pytest.raises(exception_type) as raised_exception: query_paths(**paths_args) assert "broken iou" in str(raised_exception) assert get_iou.call_count == (expected_get_iou_requests or expected_requests) assert post_paths.call_count == expected_requests
def test_post_pfs_feedback(query_paths_args): """ Test POST feedback to PFS """ feedback_token = uuid4() token_network_address = factories.make_token_network_address() route = [factories.make_address(), factories.make_address()] with patch.object(session, "post", return_value=mocked_json_response()) as feedback: post_pfs_feedback( routing_mode=RoutingMode.PFS, pfs_config=query_paths_args["pfs_config"], token_network_address=token_network_address, route=route, token=feedback_token, successful=True, ) assert feedback.called assert feedback.call_args[0][0].find( to_checksum_address(token_network_address)) > 0 payload = feedback.call_args[1]["json"] assert payload["token"] == feedback_token.hex assert payload["success"] is True assert payload["path"] == [to_checksum_address(addr) for addr in route] with patch.object(session, "post", return_value=mocked_json_response()) as feedback: post_pfs_feedback( routing_mode=RoutingMode.PFS, pfs_config=query_paths_args["pfs_config"], token_network_address=token_network_address, route=route, token=feedback_token, successful=False, ) assert feedback.called assert feedback.call_args[0][0].find( to_checksum_address(token_network_address)) > 0 payload = feedback.call_args[1]["json"] assert payload["token"] == feedback_token.hex assert payload["success"] is False assert payload["path"] == [to_checksum_address(addr) for addr in route] with patch.object(session, "post", return_value=mocked_json_response()) as feedback: post_pfs_feedback( routing_mode=RoutingMode.PRIVATE, pfs_config=query_paths_args["pfs_config"], token_network_address=token_network_address, route=route, token=feedback_token, successful=False, ) assert not feedback.called
def test_get_and_update_iou(one_to_n_address): request_args = dict( url="url", token_network_address=factories.UNIT_TOKEN_NETWORK_ADDRESS, sender=factories.make_address(), receiver=factories.make_address(), privkey=PRIVKEY, ) # RequestExceptions should be reraised as ServiceRequestFailed with pytest.raises(ServiceRequestFailed): with patch.object(session, "get", side_effect=requests.RequestException): get_last_iou(**request_args) # invalid JSON should raise a ServiceRequestFailed response = mocked_failed_response(error=ValueError) with pytest.raises(ServiceRequestFailed): with patch.object(session, "get", return_value=response): get_last_iou(**request_args) response = mocked_json_response(response_data={"other_key": "other_value"}) with patch.object(session, "get", return_value=response): iou = get_last_iou(**request_args) assert iou is None, "get_pfs_iou should return None if pfs returns no iou." last_iou = make_iou( pfs_config=PFS_CONFIG, our_address=factories.UNIT_TRANSFER_INITIATOR, privkey=PRIVKEY, block_number=10, one_to_n_address=one_to_n_address, chain_id=4, offered_fee=TokenAmount(1), ) response = mocked_json_response(response_data=dict( last_iou=last_iou.as_json())) with patch.object(session, "get", return_value=response): iou = get_last_iou(**request_args) assert iou == last_iou new_iou_1 = update_iou(replace(iou), PRIVKEY, added_amount=10) assert new_iou_1.amount == last_iou.amount + 10 assert new_iou_1.sender == last_iou.sender assert new_iou_1.receiver == last_iou.receiver assert new_iou_1.expiration_block == last_iou.expiration_block assert new_iou_1.signature is not None new_iou_2 = update_iou(replace(iou), PRIVKEY, expiration_block=45) assert new_iou_2.expiration_block == 45 assert new_iou_1.sender == iou.sender assert new_iou_1.receiver == iou.receiver assert new_iou_1.expiration_block == iou.expiration_block assert new_iou_2.signature is not None
def test_routing_mocked_pfs_invalid_json_structure(chain_state, one_to_n_address, token_network_state, our_address): token_network_state, addresses, _ = create_square_network_topology( token_network_state=token_network_state, our_address=our_address) address1, address2, address3, address4 = addresses # test routing with all nodes available chain_state.nodeaddresses_to_networkstates = { address1: NetworkState.REACHABLE, address2: NetworkState.REACHABLE, address3: NetworkState.REACHABLE, } response = mocked_json_response(response_data={}, status_code=400) with patch.object(session, "post", return_value=response): routes, feedback_token = get_best_routes_with_iou_request_mocked( chain_state=chain_state, token_network_state=token_network_state, one_to_n_address=one_to_n_address, from_address=our_address, to_address=address4, amount=50, ) # Request to PFS failed, but we do not fall back to internal routing assert len(routes) == 0 assert feedback_token is None
def happy_path_fixture(chain_state, token_network_state, our_address): token_network_state, addresses, channel_states = create_square_network_topology( token_network_state=token_network_state, our_address=our_address) address1, address2, address3, address4 = addresses chain_state.nodeaddresses_to_networkstates = { address1: NetworkState.REACHABLE, address2: NetworkState.REACHABLE, address3: NetworkState.REACHABLE, address4: NetworkState.REACHABLE, } json_data = { "result": [{ "path": [ to_checksum_address(our_address), to_checksum_address(address2), to_checksum_address(address3), to_checksum_address(address4), ], "estimated_fee": 0, }], "feedback_token": DEFAULT_FEEDBACK_TOKEN.hex, } response = mocked_json_response(response_data=json_data) return addresses, chain_state, channel_states, response, token_network_state
def iou_side_effect(*_, **kwargs): assert "params" in kwargs body = kwargs["params"] assert is_hex_address(body["sender"]) assert is_hex_address(body["receiver"]) assert "timestamp" in body assert is_hex(body["signature"]) assert len(body["signature"]) == 65 * 2 + 2 # 65 hex encoded bytes with 0x prefix return mocked_json_response(response_data=iou_json_data)
def iou_side_effect(*args, **kwargs): if args[0].endswith("/info"): return mocked_json_response({ "price_info": 5, "network_info": { "chain_id": 42, "token_network_registry_address": to_checksum_address( factories.make_token_network_registry_address()), "user_deposit_address": to_checksum_address(factories.make_address()), "confirmed_block": { "number": 11 }, }, "version": "0.0.3", "operator": "John Doe", "message": "This is your favorite pathfinding service", "payment_address": to_checksum_address(factories.make_address()), "matrix_server": "http://matrix.example.com", }) else: assert "params" in kwargs body = kwargs["params"] assert is_hex_address(body["sender"]) assert is_hex_address(body["receiver"]) assert "timestamp" in body assert is_hex(body["signature"]) assert len(body["signature"] ) == 65 * 2 + 2 # 65 hex encoded bytes with 0x prefix return mocked_json_response(response_data=iou_json_data)
def test_no_iou_when_pfs_price_0(query_paths_args): """ Test that no IOU is sent when PFS is for free """ query_paths_args["pfs_config"] = PFSConfig( info=PFSInfo( url="abc", price=TokenAmount(0), chain_id=ChainID(42), token_network_registry_address=factories. make_token_network_registry_address(), user_deposit_address=factories.make_address(), payment_address=factories.make_address(), confirmed_block_number=dict(number=BlockNumber(1)), message="", operator="", version="", matrix_server="http://matrix.example.com", matrix_room_id="!room-id:matrix.example.com", ), maximum_fee=TokenAmount(100), iou_timeout=BlockNumber(100), max_paths=5, ) with patch("raiden.network.pathfinding.get_pfs_info") as mocked_pfs_info: mocked_pfs_info.return_value = PFS_CONFIG.info with patch.object(pathfinding, "post_pfs_paths", return_value=mocked_json_response()) as post_path: query_paths( pfs_config=query_paths_args["pfs_config"], our_address=query_paths_args["our_address"], privkey=query_paths_args["privkey"], current_block_number=query_paths_args["current_block_number"], token_network_address=query_paths_args[ "token_network_address"], one_to_n_address=query_paths_args["one_to_n_address"], chain_id=query_paths_args["chain_id"], route_from=query_paths_args["route_from"], route_to=query_paths_args["route_to"], value=query_paths_args["value"], pfs_wait_for_block=query_paths_args["pfs_wait_for_block"], ) assert post_path.call_args == call( payload={ "from": to_checksum_address(query_paths_args["route_from"]), "to": to_checksum_address(query_paths_args["route_to"]), "value": query_paths_args["value"], "max_paths": query_paths_args["pfs_config"].max_paths, }, token_network_address=query_paths_args["token_network_address"], url=query_paths_args["pfs_config"].info.url, )
def test_routing_mocked_pfs_bad_http_code(chain_state, token_network_state, one_to_n_address, our_address): token_network_state, addresses, channel_states = create_square_network_topology( token_network_state=token_network_state, our_address=our_address) address1, address2, address3, address4 = addresses channel_state1, channel_state2 = channel_states # test routing with all nodes available chain_state.nodeaddresses_to_networkstates = { address1: NetworkState.REACHABLE, address2: NetworkState.REACHABLE, address3: NetworkState.REACHABLE, } # in case the pfs sends a bad http code but the correct path back json_data = { "result": [{ "path": [ to_checksum_address(our_address), to_checksum_address(address2), to_checksum_address(address3), to_checksum_address(address4), ], "fees": 0, }] } response = mocked_json_response(response_data=json_data, status_code=400) with patch.object(requests, "post", return_value=response): routes, feedback_token = get_best_routes_with_iou_request_mocked( chain_state=chain_state, token_network_state=token_network_state, one_to_n_address=one_to_n_address, from_address=our_address, to_address=address4, amount=50, ) # PFS doesn't work, so internal routing is used, so two possible routes are returned, # whereas the path via address1 is shorter ( # even if the route is not possible from a global perspective) # in case the mocked pfs response were used, we would not see address1 on the route assert routes[0].next_hop_address == address1 assert routes[0].forward_channel_id == channel_state1.identifier assert routes[1].next_hop_address == address2 assert routes[1].forward_channel_id == channel_state2.identifier assert feedback_token is None
def test_routing_mocked_pfs_unavailable_peer( chain_state, token_network_state, one_to_n_address, our_address ): token_network_state, addresses, channel_states = create_square_network_topology( token_network_state=token_network_state, our_address=our_address ) address1, address2, address3, address4 = addresses _, channel_state2 = channel_states json_data = { "result": [ { "path": [ to_checksum_address(our_address), to_checksum_address(address2), to_checksum_address(address3), to_checksum_address(address4), ], "estimated_fee": 0, } ], "feedback_token": DEFAULT_FEEDBACK_TOKEN.hex, } # test routing with node 2 unavailable chain_state.nodeaddresses_to_networkstates = { address1: NetworkState.REACHABLE, address2: NetworkState.UNREACHABLE, address3: NetworkState.REACHABLE, } response = mocked_json_response(response_data=json_data, status_code=200) with patch.object(requests, "post", return_value=response): routes, feedback_token = get_best_routes_with_iou_request_mocked( chain_state=chain_state, token_network_state=token_network_state, one_to_n_address=one_to_n_address, from_address=our_address, to_address=address4, amount=50, ) # Node with address2 is not reachable, so even if the only route sent by the PFS # is over address2, the internal routing does not provide assert routes[0].next_hop_address == address2 assert routes[0].forward_channel_id == channel_state2.identifier assert feedback_token == DEFAULT_FEEDBACK_TOKEN
def test_no_iou_when_pfs_price_0(query_paths_args): """ Test that no IOU is sent when PFS is for free """ query_paths_args["pfs_config"] = PFSConfig( info=PFSInfo( url="abc", price=TokenAmount(0), chain_id=ChainID(42), token_network_registry_address=factories. make_token_network_address(), payment_address=factories.make_address(), message="", operator="", version="", ), maximum_fee=TokenAmount(100), iou_timeout=BlockNumber(100), max_paths=5, ) with patch.object(pathfinding, "post_pfs_paths", return_value=mocked_json_response()) as post_path: query_paths( pfs_config=query_paths_args["pfs_config"], our_address=query_paths_args["our_address"], privkey=query_paths_args["privkey"], current_block_number=query_paths_args["current_block_number"], token_network_address=query_paths_args["token_network_address"], one_to_n_address=query_paths_args["one_to_n_address"], chain_id=query_paths_args["chain_id"], route_from=query_paths_args["route_from"], route_to=query_paths_args["route_to"], value=query_paths_args["value"], ) assert post_path.call_args == call( payload={ "from": to_checksum_address(query_paths_args["route_from"]), "to": to_checksum_address(query_paths_args["route_to"]), "value": query_paths_args["value"], "max_paths": query_paths_args["pfs_config"].max_paths, }, token_network_address=query_paths_args["token_network_address"], url=query_paths_args["pfs_config"].info.url, )
def test_routing_mocked_pfs_bad_http_code( chain_state, token_network_state, one_to_n_address, our_address ): token_network_state, addresses, _ = create_square_network_topology( token_network_state=token_network_state, our_address=our_address ) address1, address2, address3, address4 = addresses # test routing with all nodes available chain_state.nodeaddresses_to_networkstates = { address1: NetworkState.REACHABLE, address2: NetworkState.REACHABLE, address3: NetworkState.REACHABLE, } # in case the pfs sends a bad http code but the correct path back json_data = { "result": [ { "path": [ to_checksum_address(our_address), to_checksum_address(address2), to_checksum_address(address3), to_checksum_address(address4), ], "fees": 0, } ] } response = mocked_json_response(response_data=json_data, status_code=400) with patch.object(requests, "post", return_value=response): routes, feedback_token = get_best_routes_with_iou_request_mocked( chain_state=chain_state, token_network_state=token_network_state, one_to_n_address=one_to_n_address, from_address=our_address, to_address=address4, amount=50, ) # Request to PFS failed, but we do not fall back to internal routing assert len(routes) == 0 assert feedback_token is None
def test_configure_pfs(service_registry_address, private_keys, web3, contract_manager): chain_id = ChainID(int(web3.net.version)) service_registry, urls = deploy_service_registry_and_set_urls( private_keys=private_keys, web3=web3, contract_manager=contract_manager, service_registry_address=service_registry_address, ) json_data = { "price_info": 0, "network_info": { "chain_id": chain_id, "token_network_registry_address": to_checksum_address(token_network_registry_address_test_default), "user_deposit_address": to_checksum_address(privatekey_to_address(private_keys[1])), "confirmed_block": { "number": 10 }, }, "message": "This is your favorite pathfinding service", "operator": "John Doe", "version": "0.0.1", "payment_address": to_checksum_address(privatekey_to_address(private_keys[0])), } response = mocked_json_response(response_data=json_data) # With local routing configure_pfs should raise assertion with pytest.raises(AssertionError): _ = configure_pfs_or_exit( pfs_url="", routing_mode=RoutingMode.LOCAL, service_registry=service_registry, node_network_id=chain_id, token_network_registry_address= token_network_registry_address_test_default, pathfinding_max_fee=DEFAULT_PATHFINDING_MAX_FEE, ) # With private routing configure_pfs should raise assertion with pytest.raises(AssertionError): _ = configure_pfs_or_exit( pfs_url="", routing_mode=RoutingMode.PRIVATE, service_registry=service_registry, node_network_id=chain_id, token_network_registry_address= token_network_registry_address_test_default, pathfinding_max_fee=DEFAULT_PATHFINDING_MAX_FEE, ) # Asking for auto address # To make this deterministic we need to patch the random selection function patch_random = patch("raiden.network.pathfinding.get_random_pfs", return_value="http://foo") with patch.object(requests, "get", return_value=response), patch_random: config = configure_pfs_or_exit( pfs_url=MATRIX_AUTO_SELECT_SERVER, routing_mode=RoutingMode.PFS, service_registry=service_registry, node_network_id=chain_id, token_network_registry_address= token_network_registry_address_test_default, pathfinding_max_fee=DEFAULT_PATHFINDING_MAX_FEE, ) assert config.url in urls assert is_canonical_address(config.payment_address) # Configuring a valid given address given_address = "http://foo" with patch.object(requests, "get", return_value=response): config = configure_pfs_or_exit( pfs_url=given_address, routing_mode=RoutingMode.PFS, service_registry=service_registry, node_network_id=chain_id, token_network_registry_address= token_network_registry_address_test_default, pathfinding_max_fee=DEFAULT_PATHFINDING_MAX_FEE, ) assert config.url == given_address assert is_same_address(config.payment_address, json_data["payment_address"]) assert config.price == json_data["price_info"] # Bad address, should exit the program bad_address = "http://badaddress" with pytest.raises(RaidenError): with patch.object(requests, "get", side_effect=requests.RequestException()): # Configuring a given address _ = configure_pfs_or_exit( pfs_url=bad_address, routing_mode=RoutingMode.PFS, service_registry=service_registry, node_network_id=chain_id, token_network_registry_address= token_network_registry_address_test_default, pathfinding_max_fee=DEFAULT_PATHFINDING_MAX_FEE, ) # Addresses of token network registries of pfs and client conflict, should exit the client response = mocked_json_response(response_data=json_data) with pytest.raises(RaidenError): with patch.object(requests, "get", return_value=response): _ = configure_pfs_or_exit( pfs_url="http://foo", routing_mode=RoutingMode.PFS, service_registry=service_registry, node_network_id=chain_id, token_network_registry_address=TokenNetworkRegistryAddress( to_canonical_address( "0x2222222222222222222222222222222222222221")), pathfinding_max_fee=DEFAULT_PATHFINDING_MAX_FEE, ) # ChainIDs of pfs and client conflict, should exit the client response = mocked_json_response(response_data=json_data) with pytest.raises(RaidenError): with patch.object(requests, "get", return_value=response): configure_pfs_or_exit( pfs_url="http://foo", routing_mode=RoutingMode.PFS, service_registry=service_registry, node_network_id=ChainID(chain_id + 1), token_network_registry_address= token_network_registry_address_test_default, pathfinding_max_fee=DEFAULT_PATHFINDING_MAX_FEE, )
def mocked_json_response_with_sleep(**kwargs): # pylint: disable=unused-argument gevent.sleep(0.2) return mocked_json_response()