Example #1
0
def get_pfs_info(url: str) -> PFSInfo:
    try:
        response = requests.get(f"{url}/api/v1/info",
                                timeout=DEFAULT_HTTP_REQUEST_TIMEOUT)
        infos = get_response_json(response)

        return PFSInfo(
            url=url,
            price=infos["price_info"],
            chain_id=infos["network_info"]["chain_id"],
            token_network_registry_address=TokenNetworkRegistryAddress(
                to_canonical_address(
                    infos["network_info"]["token_network_registry_address"])),
            user_deposit_address=Address(
                to_canonical_address(
                    infos["network_info"]["user_deposit_address"])),
            payment_address=to_canonical_address(infos["payment_address"]),
            message=infos["message"],
            operator=infos["operator"],
            version=infos["version"],
        )
    except requests.exceptions.RequestException as e:
        msg = "Selected Pathfinding Service did not respond"
        raise ServiceRequestFailed(msg) from e
    except (json.JSONDecodeError, requests.exceptions.RequestException,
            KeyError) as e:
        msg = "Selected Pathfinding Service returned unexpected reply"
        raise ServiceRequestFailed(msg) from e
Example #2
0
def get_pfs_info(url: str) -> PFSInfo:
    try:
        response = session.get(f"{url}/api/v1/info")
        infos = get_response_json(response)
        matrix_server_info = urlparse(infos["matrix_server"])

        return PFSInfo(
            url=url,
            price=infos["price_info"],
            chain_id=infos["network_info"]["chain_id"],
            token_network_registry_address=TokenNetworkRegistryAddress(
                to_canonical_address(infos["network_info"]["token_network_registry_address"])
            ),
            user_deposit_address=Address(
                to_canonical_address(infos["network_info"]["user_deposit_address"])
            ),
            payment_address=to_canonical_address(infos["payment_address"]),
            message=infos["message"],
            operator=infos["operator"],
            version=infos["version"],
            confirmed_block_number=infos["network_info"]["confirmed_block"]["number"],
            matrix_server=matrix_server_info.netloc,
            matrix_room_id=infos.get("matrix_room_id"),
        )
    except RequestException as e:
        msg = "Selected Pathfinding Service did not respond"
        raise ServiceRequestFailed(msg) from e
    except (json.JSONDecodeError, KeyError, ValueError) as e:
        msg = "Selected Pathfinding Service returned unexpected reply"
        raise ServiceRequestFailed(msg) from e
Example #3
0
def post_pfs_paths(
        url: str, token_network_address: TokenNetworkAddress,
        payload: Dict[str, Any]) -> Tuple[List[Dict[str, Any]], UUID]:
    try:
        response = requests.post(
            f"{url}/api/v1/{to_checksum_address(token_network_address)}/paths",
            json=payload,
            timeout=DEFAULT_HTTP_REQUEST_TIMEOUT,
        )
    except requests.RequestException as e:
        raise ServiceRequestFailed(
            f"Could not connect to Pathfinding Service ({str(e)})",
            dict(parameters=payload, exc_info=True),
        )

    if response.status_code != 200:
        info = {"http_error": response.status_code}
        try:
            response_json = get_response_json(response)
        except ValueError:
            log.error(
                "Pathfinding Service returned error code (malformed json in response)",
                response=response,
            )
            raise ServiceRequestFailed(
                "Pathfinding Service returned error code (malformed json in response)",
                info)

        error = info["error"] = response_json.get("errors")
        error_code = info["error_code"] = response_json.get("error_code", 0)

        if PFSError.is_iou_rejected(error_code):
            raise ServiceRequestIOURejected(error, error_code)

        raise ServiceRequestFailed(error, info)

    try:
        response_json = get_response_json(response)
        return response_json["result"], UUID(response_json["feedback_token"])
    except KeyError:
        raise ServiceRequestFailed(
            "Answer from Pathfinding Service not understood ('result' field missing)",
            dict(response=get_response_json(response)),
        )
    except ValueError:
        raise ServiceRequestFailed(
            "Pathfinding Service returned invalid json",
            dict(response_text=response.text, exc_info=True),
        )
Example #4
0
def update_iou(
    iou: IOU,
    privkey: bytes,
    added_amount: TokenAmount = ZERO_TOKENS,
    expiration_block: Optional[BlockNumber] = None,
) -> IOU:

    expected_signature = Signature(
        sign_one_to_n_iou(
            privatekey=to_hex(privkey),
            sender=to_checksum_address(iou.sender),
            receiver=to_checksum_address(iou.receiver),
            amount=iou.amount,
            expiration_block=iou.expiration_block,
            one_to_n_address=to_checksum_address(iou.one_to_n_address),
            chain_id=iou.chain_id,
        ))
    if iou.signature != expected_signature:
        raise ServiceRequestFailed(
            "Last IOU as given by the Pathfinding Service is invalid (signature does not match)"
        )

    iou.amount = TokenAmount(iou.amount + added_amount)
    if expiration_block:
        iou.expiration_block = expiration_block

    iou.sign(privkey)

    return iou
Example #5
0
def update_iou(
    iou: typing.Dict[str, typing.Any],
    privkey: bytes,
    added_amount: typing.TokenAmount = 0,
    expiration_block: typing.Optional[typing.BlockNumber] = None,
) -> typing.Dict[str, typing.Any]:

    expected_signature = to_hex(
        sign_one_to_n_iou(
            privatekey=to_hex(privkey),
            expiration=iou['expiration_block'],
            sender=iou['sender'],
            receiver=iou['receiver'],
            amount=iou['amount'],
        ))
    if iou.get('signature') != expected_signature:
        raise ServiceRequestFailed(
            'Last IOU as given by the pathfinding service is invalid (signature does not match)',
        )

    iou['amount'] += added_amount
    if expiration_block:
        iou['expiration_block'] = expiration_block

    iou['signature'] = sign_one_to_n_iou(
        privatekey=to_hex(privkey),
        expiration=iou['expiration_block'],
        sender=iou['sender'],
        receiver=iou['receiver'],
        amount=iou['amount'],
    )

    return iou
Example #6
0
def get_last_iou(
    url: str,
    token_network_address: typing.Union[typing.TokenNetworkAddress,
                                        typing.TokenNetworkID],
    sender: typing.Address,
    receiver: typing.Address,
) -> typing.Optional[typing.Dict]:

    timestamp = datetime.utcnow().isoformat(timespec='seconds')
    signature = to_hex(
        eth_sign_hash_message(
            Web3.toBytes(hexstr=sender) + Web3.toBytes(hexstr=receiver) +
            bytes(timestamp, 'utf-8'), ))

    try:
        return requests.get(
            f'{url}/api/v1/{to_checksum_address(token_network_address)}/payment/iou',
            data=dict(sender=sender,
                      receiver=receiver,
                      timestamp=timestamp,
                      signature=signature),
            timeout=DEFAULT_HTTP_REQUEST_TIMEOUT,
        ).json().get('last_iou')
    except (requests.exceptions.RequestException, ValueError) as e:
        raise ServiceRequestFailed(str(e))
Example #7
0
def update_iou(
    iou: Dict[str, Any],
    privkey: bytes,
    added_amount: TokenAmount = ZERO_TOKENS,
    expiration_block: Optional[BlockNumber] = None,
) -> Dict[str, Any]:

    expected_signature = to_hex(
        sign_one_to_n_iou(
            privatekey=to_hex(privkey),
            expiration=iou["expiration_block"],
            sender=iou["sender"],
            receiver=iou["receiver"],
            amount=iou["amount"],
        ))
    if iou.get("signature") != expected_signature:
        raise ServiceRequestFailed(
            "Last IOU as given by the pathfinding service is invalid (signature does not match)"
        )

    iou["amount"] += added_amount
    if expiration_block:
        iou["expiration_block"] = expiration_block

    iou["signature"] = to_hex(
        sign_one_to_n_iou(
            privatekey=to_hex(privkey),
            expiration=iou["expiration_block"],
            sender=iou["sender"],
            receiver=iou["receiver"],
            amount=iou["amount"],
        ))

    return iou
Example #8
0
def get_last_iou(
    url: str,
    token_network_address: Union[TokenNetworkAddress, TokenNetworkID],
    sender: Address,
    receiver: Address,
    privkey: bytes,
) -> Optional[Dict]:

    timestamp = datetime.utcnow().isoformat(timespec="seconds")
    signature_data = sender + receiver + Web3.toBytes(text=timestamp)
    signature = to_hex(LocalSigner(privkey).sign(signature_data))

    try:
        return (requests.get(
            f"{url}/api/v1/{to_checksum_address(token_network_address)}/payment/iou",
            params=dict(
                sender=to_checksum_address(sender),
                receiver=to_checksum_address(receiver),
                timestamp=timestamp,
                signature=signature,
            ),
            timeout=DEFAULT_HTTP_REQUEST_TIMEOUT,
        ).json().get("last_iou"))
    except (requests.exceptions.RequestException, ValueError) as e:
        raise ServiceRequestFailed(str(e))
Example #9
0
def post_pfs_paths(url, token_network_address, payload):
    try:
        response = requests.post(
            f'{url}/api/v1/{to_checksum_address(token_network_address)}/paths',
            json=payload,
            timeout=DEFAULT_HTTP_REQUEST_TIMEOUT,
        )
    except requests.RequestException as e:
        raise ServiceRequestFailed(
            f'Could not connect to Pathfinding Service ({str(e)})',
            dict(parameters=payload, exc_info=True),
        )

    if response.status_code != 200:
        info = {'http_error': response.status_code}
        try:
            response_json = response.json()
        except ValueError:
            raise ServiceRequestFailed(
                'Pathfinding service returned error code (malformed json in response)',
                info,
            )
        else:
            error = info['error'] = response_json.get('errors')
            error_code = info['error_code'] = response_json.get(
                'error_code', 0)
            if PFSError.is_iou_rejected(error_code):
                raise ServiceRequestIOURejected(error, error_code)

        raise ServiceRequestFailed('Pathfinding service returned error code',
                                   info)

    try:
        return response.json()['result']
    except KeyError:
        raise ServiceRequestFailed(
            "Answer from pathfinding service not understood ('result' field missing)",
            dict(response=response.json()),
        )
    except ValueError:
        raise ServiceRequestFailed(
            'Pathfinding service returned invalid json',
            dict(response_text=response.text, exc_info=True),
        )
Example #10
0
def post_pfs_paths(
    url: str, token_network_address: TokenNetworkAddress, payload: Dict[str, Any]
) -> Tuple[List[Dict[str, Any]], UUID]:
    try:
        response = session.post(
            f"{url}/api/v1/{to_checksum_address(token_network_address)}/paths", json=payload
        )
    except RequestException as e:
        raise ServiceRequestFailed(
            f"Could not connect to Pathfinding Service ({str(e)})",
            dict(parameters=payload, exc_info=True),
        )

    if response.status_code != 200:
        try:
            response_json = get_response_json(response)
        except ValueError:
            log.error(
                "Pathfinding Service returned error code (malformed json in response)",
                response=response,
            )
            raise ServiceRequestFailed(
                "Pathfinding Service returned error code (malformed json in response)",
                {"http_error": response.status_code},
            )

        raise PFSReturnedError.from_response(response_json)

    try:
        response_json = get_response_json(response)
        return response_json["result"], UUID(response_json["feedback_token"])
    except KeyError:
        raise ServiceRequestFailed(
            "Answer from Pathfinding Service not understood ('result' field missing)",
            dict(response=get_response_json(response)),
        )
    except ValueError:
        raise ServiceRequestFailed(
            "Pathfinding Service returned invalid json",
            dict(response_text=response.text, exc_info=True),
        )
Example #11
0
def get_last_iou(
    url: str,
    token_network_address: TokenNetworkAddress,
    sender: Address,
    receiver: Address,
    privkey: bytes,
) -> Optional[IOU]:

    timestamp = datetime.utcnow().isoformat(timespec="seconds")
    signature_data = sender + receiver + Web3.toBytes(text=timestamp)
    signature = to_hex(LocalSigner(privkey).sign(signature_data))

    try:
        response = requests.get(
            f"{url}/api/v1/{to_checksum_address(token_network_address)}/payment/iou",
            params=dict(
                sender=to_checksum_address(sender),
                receiver=to_checksum_address(receiver),
                timestamp=timestamp,
                signature=signature,
            ),
            timeout=DEFAULT_HTTP_REQUEST_TIMEOUT,
        )

        data = json.loads(response.content).get("last_iou")

        if data is None:
            return None

        return IOU(
            sender=to_canonical_address(data["sender"]),
            receiver=to_canonical_address(data["receiver"]),
            one_to_n_address=OneToNAddress(
                to_canonical_address(data["one_to_n_address"])),
            amount=data["amount"],
            expiration_block=data["expiration_block"],
            chain_id=data["chain_id"],
            signature=Signature(decode_hex(data["signature"])),
        )
    except (requests.exceptions.RequestException, ValueError, KeyError) as e:
        raise ServiceRequestFailed(str(e))
Example #12
0
def get_pfs_info(url: str) -> PFSInfo:
    try:
        response = requests.get(f"{url}/api/v1/info",
                                timeout=DEFAULT_HTTP_REQUEST_TIMEOUT)
        infos = get_response_json(response)

        return PFSInfo(
            url=url,
            price=infos["price_info"],
            chain_id=infos["network_info"]["chain_id"],
            token_network_registry_address=TokenNetworkRegistryAddress(
                to_canonical_address(
                    infos["network_info"]["registry_address"])),
            payment_address=to_canonical_address(infos["payment_address"]),
            message=infos["message"],
            operator=infos["operator"],
            version=infos["version"],
        )
    except (json.JSONDecodeError, requests.exceptions.RequestException,
            KeyError) as e:
        raise ServiceRequestFailed(str(e)) from e
Example #13
0
def query_paths(
    pfs_config: PFSConfig,
    our_address: Address,
    privkey: bytes,
    current_block_number: BlockNumber,
    token_network_address: TokenNetworkAddress,
    one_to_n_address: OneToNAddress,
    chain_id: ChainID,
    route_from: InitiatorAddress,
    route_to: TargetAddress,
    value: PaymentAmount,
) -> Tuple[List[Dict[str, Any]], Optional[UUID]]:
    """ Query paths from the PFS.

    Send a request to the /paths endpoint of the PFS specified in service_config, and
    retry in case of a failed request if it makes sense.
    """

    payload = {
        "from": to_checksum_address(route_from),
        "to": to_checksum_address(route_to),
        "value": value,
        "max_paths": pfs_config.max_paths,
    }
    offered_fee = pfs_config.info.price
    scrap_existing_iou = False

    for retries in reversed(range(MAX_PATHS_QUERY_ATTEMPTS)):
        # Since the IOU amount is monotonically increasing, only a single
        # greenlet can be in charge of increasing it at the same time. Using a
        # semaphore in this primitive way limits the speed at which we can do
        # simultaneous transfers.
        # See https://github.com/raiden-network/raiden/issues/5647
        with gevent.lock.Semaphore():
            if offered_fee > 0:
                new_iou = create_current_iou(
                    pfs_config=pfs_config,
                    token_network_address=token_network_address,
                    one_to_n_address=one_to_n_address,
                    our_address=our_address,
                    privkey=privkey,
                    chain_id=chain_id,
                    block_number=current_block_number,
                    offered_fee=offered_fee,
                    scrap_existing_iou=scrap_existing_iou,
                )
                payload["iou"] = new_iou.as_json()

            log.info(
                "Requesting paths from Pathfinding Service",
                url=pfs_config.info.url,
                token_network_address=to_checksum_address(
                    token_network_address),
                payload=payload,
            )

            try:
                return post_pfs_paths(
                    url=pfs_config.info.url,
                    token_network_address=token_network_address,
                    payload=payload,
                )
            except ServiceRequestIOURejected as error:
                code = error.error_code
                log.debug("Pathfinding Service rejected IOU", error=error)

                if retries == 0 or code in (
                        PFSError.WRONG_IOU_RECIPIENT,
                        PFSError.DEPOSIT_TOO_LOW,
                ):
                    raise
                elif code in (PFSError.IOU_ALREADY_CLAIMED,
                              PFSError.IOU_EXPIRED_TOO_EARLY):
                    scrap_existing_iou = True
                elif code == PFSError.INSUFFICIENT_SERVICE_PAYMENT:
                    try:
                        new_info = get_pfs_info(pfs_config.info.url)
                    except ServiceRequestFailed:
                        raise ServiceRequestFailed(
                            "Could not get updated fee information from Pathfinding Service."
                        )
                    if new_info.price > pfs_config.maximum_fee:
                        raise ServiceRequestFailed("PFS fees too high.")
                    log.info(f"Pathfinding Service increased fees",
                             new_price=new_info.price)
                    pfs_config.info = new_info
                elif code == PFSError.NO_ROUTE_FOUND:
                    log.info(
                        f"Pathfinding Service can not find a route: {error}.")
                    return list(), None
                log.info(
                    f"Pathfinding Service rejected our payment. Reason: {error}. Attempting again."
                )

    # If we got no results after MAX_PATHS_QUERY_ATTEMPTS return empty list of paths
    return list(), None
Example #14
0
def query_paths(
    pfs_config: PFSConfig,
    our_address: Address,
    privkey: bytes,
    current_block_number: BlockNumber,
    token_network_address: TokenNetworkAddress,
    one_to_n_address: Address,
    chain_id: ChainID,
    route_from: InitiatorAddress,
    route_to: TargetAddress,
    value: PaymentAmount,
) -> Tuple[List[Dict[str, Any]], Optional[UUID]]:
    """ Query paths from the PFS.

    Send a request to the /paths endpoint of the PFS specified in service_config, and
    retry in case of a failed request if it makes sense.
    """

    payload = {
        "from": to_checksum_address(route_from),
        "to": to_checksum_address(route_to),
        "value": value,
        "max_paths": pfs_config.max_paths,
    }
    offered_fee = pfs_config.info.price
    scrap_existing_iou = False

    for retries in reversed(range(MAX_PATHS_QUERY_ATTEMPTS)):
        if offered_fee > 0:
            new_iou = create_current_iou(
                pfs_config=pfs_config,
                token_network_address=token_network_address,
                one_to_n_address=one_to_n_address,
                our_address=our_address,
                privkey=privkey,
                chain_id=chain_id,
                block_number=current_block_number,
                offered_fee=offered_fee,
                scrap_existing_iou=scrap_existing_iou,
            )
            payload["iou"] = new_iou.as_json()

        log.info(
            "Requesting paths from Pathfinding Service",
            url=pfs_config.info.url,
            token_network_address=token_network_address,
            payload=payload,
        )

        try:
            return post_pfs_paths(
                url=pfs_config.info.url,
                token_network_address=token_network_address,
                payload=payload,
            )
        except ServiceRequestIOURejected as error:
            code = error.error_code
            if retries == 0 or code in (PFSError.WRONG_IOU_RECIPIENT,
                                        PFSError.DEPOSIT_TOO_LOW):
                raise
            elif code in (PFSError.IOU_ALREADY_CLAIMED,
                          PFSError.IOU_EXPIRED_TOO_EARLY):
                scrap_existing_iou = True
            elif code == PFSError.INSUFFICIENT_SERVICE_PAYMENT:
                try:
                    new_info = get_pfs_info(pfs_config.info.url)
                except ServiceRequestFailed:
                    raise ServiceRequestFailed(
                        "Could not get updated fees from PFS.")
                if new_info.price > pfs_config.maximum_fee:
                    raise ServiceRequestFailed("PFS fees too high.")
                log.info(f"PFS increased fees", new_price=new_info.price)
                pfs_config.info = new_info
            elif code == PFSError.NO_ROUTE_FOUND:
                log.info(f"PFS cannot find a route: {error}.")
                return list(), None
            log.info(
                f"PFS rejected our IOU, reason: {error}. Attempting again.")

    # If we got no results after MAX_PATHS_QUERY_ATTEMPTS return empty list of paths
    return list(), None