Пример #1
0
 def upsert_iou(self, iou: IOU) -> None:
     iou_dict = IOU.Schema(exclude=["receiver", "chain_id"]).dump(iou)
     iou_dict["one_to_n_address"] = to_checksum_address(
         iou_dict["one_to_n_address"])
     for key in ("amount", "expiration_block"):
         iou_dict[key] = hex256(iou_dict[key])
     self.upsert("iou", iou_dict)
Пример #2
0
    def get_ious(
        self,
        sender: Address = None,
        expiration_block: BlockNumber = None,
        claimed: bool = None,
        expires_before: BlockNumber = None,
        amount_at_least: TokenAmount = None,
    ) -> Iterator[IOU]:
        query = """
            SELECT *, (SELECT chain_id FROM blockchain) AS chain_id
            FROM iou
            WHERE 1=1
        """
        args: list = []
        if sender is not None:
            query += " AND sender = ?"
            args.append(to_checksum_address(sender))
        if expiration_block is not None:
            query += " AND expiration_block = ?"
            args.append(hex256(expiration_block))
        if claimed is not None:
            query += " AND claimed = ?"
            args.append(claimed)
        if expires_before is not None:
            query += " AND expiration_block < ?"
            args.append(hex256(expires_before))
        if amount_at_least is not None:
            query += " AND amount >= ?"
            args.append(hex256(amount_at_least))

        for row in self.conn.execute(query, args):
            iou_dict = dict(zip(row.keys(), row))
            iou_dict["receiver"] = to_checksum_address(self.pfs_address)
            yield IOU.Schema().load(iou_dict)
Пример #3
0
 def upsert_iou(self, iou: IOU):
     iou_dict = IOU.Schema(strict=True).dump(iou)[0]
     self.conn.execute("""
         INSERT OR REPLACE INTO iou (
             sender, amount, expiration_block, signature, claimed
         ) VALUES (:sender, :amount, :expiration_block, :signature, :claimed)
     """, iou_dict)
Пример #4
0
def test_get_iou(api_sut: PFSApi, api_url: str, token_network_model: TokenNetwork, make_iou):
    privkey = PrivateKey(get_random_privkey())
    sender = private_key_to_address(privkey)
    url = api_url + f"/v1/{to_checksum_address(token_network_model.address)}/payment/iou"

    def make_params(timestamp: str):
        params = {
            "sender": to_checksum_address(sender),
            "receiver": to_checksum_address(api_sut.pathfinding_service.address),
            "timestamp": timestamp,
        }
        local_signer = LocalSigner(private_key=privkey)
        params["signature"] = encode_hex(
            local_signer.sign(
                to_canonical_address(params["sender"])
                + to_canonical_address(params["receiver"])
                + params["timestamp"].encode("utf8")
            )
        )
        return params

    # Request without IOU in database
    params = make_params(datetime.utcnow().isoformat())
    response = requests.get(url, params=params)
    assert response.status_code == 404, response.json()
    assert response.json() == {"last_iou": None}

    # Add IOU to database
    iou = make_iou(
        privkey, api_sut.pathfinding_service.address, one_to_n_address=api_sut.one_to_n_address
    )
    iou.claimed = False
    api_sut.pathfinding_service.database.upsert_iou(iou)

    # Is returned IOU the one save into the db?
    response = requests.get(url, params=params)
    assert response.status_code == 200, response.json()
    iou_dict = IOU.Schema(exclude=["claimed"]).dump(iou)
    assert response.json()["last_iou"] == iou_dict

    # Invalid signatures must fail
    params["signature"] = encode_hex((int(params["signature"], 16) + 1).to_bytes(65, "big"))
    response = requests.get(url, params=params)
    assert response.status_code == 400, response.json()
    assert response.json()["error_code"] == exceptions.InvalidSignature.error_code

    # Timestamp must no be too old to prevent replay attacks
    old_timestamp = datetime.utcnow() - timedelta(days=1)
    params = make_params(old_timestamp.isoformat())
    response = requests.get(url, params=params)
    assert response.status_code == 400, response.json()
    assert response.json()["error_code"] == exceptions.RequestOutdated.error_code

    # Timestamp with timezone info is invalid
    for timestamp in (datetime.now(tz=timezone.utc).isoformat(), "2019-11-07T12:52:25.079Z"):
        params = make_params(timestamp)
        response = requests.get(url, params=params)
        assert response.status_code == 400, response.json()
        assert response.json()["error_code"] == exceptions.InvalidRequest.error_code
Пример #5
0
def test_load_and_save_iou(pathfinding_service_mocked_listeners):
    pfs = pathfinding_service_mocked_listeners
    iou_dict = make_iou(get_random_privkey(), pfs.address)
    iou = IOU.Schema().load(iou_dict)[0]
    iou.claimed = False
    pfs.database.upsert_iou(iou)
    stored_iou = pfs.database.get_iou(iou.sender, iou.expiration_block)
    assert stored_iou == iou
Пример #6
0
def test_get_iou(api_sut: ServiceApi, api_url: str,
                 token_network_model: TokenNetwork, make_iou):
    privkey = get_random_privkey()
    sender = private_key_to_address(privkey)
    url = api_url + f"/{to_checksum_address(token_network_model.address)}/payment/iou"

    def make_params(timestamp: datetime):
        params = {
            "sender": to_checksum_address(sender),
            "receiver":
            to_checksum_address(api_sut.pathfinding_service.address),
            "timestamp": timestamp.isoformat(),
        }
        local_signer = LocalSigner(private_key=decode_hex(privkey))
        params["signature"] = encode_hex(
            local_signer.sign(
                pack_data(
                    (params["sender"], "address"),
                    (params["receiver"], "address"),
                    (params["timestamp"], "string"),
                )))
        return params

    # Request without IOU in database
    params = make_params(datetime.utcnow())
    response = requests.get(url, params=params)
    assert response.status_code == 404, response.json()
    assert response.json() == {"last_iou": None}

    # Add IOU to database
    iou = make_iou(privkey,
                   api_sut.pathfinding_service.address,
                   one_to_n_address=api_sut.one_to_n_address)
    iou.claimed = False
    api_sut.pathfinding_service.database.upsert_iou(iou)

    # Is returned IOU the one save into the db?
    response = requests.get(url, params=params)
    assert response.status_code == 200, response.json()
    iou_dict = IOU.Schema(exclude=["claimed"]).dump(iou)
    assert response.json()["last_iou"] == iou_dict

    # Invalid signatures must fail
    params["signature"] = encode_hex(
        (int(params["signature"], 16) + 1).to_bytes(65, "big"))
    response = requests.get(url, params=params)
    assert response.status_code == 400, response.json()
    assert response.json(
    )["error_code"] == exceptions.InvalidSignature.error_code

    # Timestamp must no be too old to prevent replay attacks
    params = make_params(datetime.utcnow() - timedelta(days=1))
    response = requests.get(url, params=params)
    assert response.status_code == 400, response.json()
    assert response.json(
    )["error_code"] == exceptions.RequestOutdated.error_code
Пример #7
0
def test_get_iou(
    api_sut: ServiceApi, api_url: str, pathfinding_service_mock, token_network_model: TokenNetwork
):
    privkey = get_random_privkey()
    sender = private_key_to_address(privkey)
    url = api_url + f'/{token_network_model.address}/payment/iou'

    def make_params(timestamp: datetime):
        params = {
            'sender': sender,
            'receiver': api_sut.pathfinding_service.address,
            'timestamp': timestamp.isoformat(),
        }
        local_signer = LocalSigner(private_key=decode_hex(privkey))
        params['signature'] = encode_hex(
            local_signer.sign(
                pack_data(
                    ['address', 'address', 'string'],
                    [params['sender'], params['receiver'], params['timestamp']],
                )
            )
        )
        return params

    # Request without IOU in database
    params = make_params(datetime.utcnow())
    response = requests.get(url, params=params)
    assert response.status_code == 404, response.json()
    assert response.json() == {'last_iou': None}

    # Add IOU to database
    iou = make_iou(privkey, api_sut.pathfinding_service.address)
    iou.claimed = False
    api_sut.pathfinding_service.database.upsert_iou(iou)

    # Is returned IOU the one save into the db?
    response = requests.get(url, params=params)
    assert response.status_code == 200, response.json()
    iou_dict = IOU.Schema(exclude=['claimed']).dump(iou)[0]
    assert response.json()['last_iou'] == iou_dict

    # Invalid signatures must fail
    params['signature'] = encode_hex((int(params['signature'], 16) + 1).to_bytes(65, 'big'))
    response = requests.get(url, params=params)
    assert response.status_code == 400, response.json()
    assert response.json()['error_code'] == exceptions.InvalidSignature.error_code

    # Timestamp must no be too old to prevent replay attacks
    params = make_params(datetime.utcnow() - timedelta(days=1))
    response = requests.get(url, params=params)
    assert response.status_code == 400, response.json()
    assert response.json()['error_code'] == exceptions.RequestOutdated.error_code

    # kill all running greenlets
    gevent.killall([obj for obj in gc.get_objects() if isinstance(obj, gevent.Greenlet)])
Пример #8
0
def process_payment(iou_dict: dict, pathfinding_service: PathfindingService):
    if pathfinding_service.service_fee == 0:
        return
    if iou_dict is None:
        raise exceptions.MissingIOU

    # Basic IOU validity checks
    iou, errors = IOU.Schema().load(iou_dict)
    if errors:
        raise exceptions.InvalidRequest(**errors)
    if iou.receiver != pathfinding_service.address:
        raise exceptions.WrongIOURecipient(
            expected=pathfinding_service.address)
    if not iou.is_signature_valid():
        raise exceptions.InvalidSignature

    # Compare with known IOU
    active_iou = pathfinding_service.database.get_iou(
        sender=iou.sender,
        claimed=False,
    )
    if active_iou:
        if active_iou.expiration_block != iou.expiration_block:
            raise exceptions.UseThisIOU(iou=active_iou)

        expected_amount = active_iou.amount + pathfinding_service.service_fee
    else:
        claimed_iou = pathfinding_service.database.get_iou(
            sender=iou.sender,
            expiration_block=iou.expiration_block,
            claimed=True,
        )
        if claimed_iou:
            raise exceptions.IOUAlreadyClaimed

        min_expiry = pathfinding_service.web3.eth.blockNumber + MIN_IOU_EXPIRY
        if iou.expiration_block < min_expiry:
            raise exceptions.IOUExpiredTooEarly(min_expiry=min_expiry)
        expected_amount = pathfinding_service.service_fee
    if iou.amount < expected_amount:
        raise exceptions.InsufficientServicePayment(
            expected_amount=expected_amount)

    # Check client's deposit in UserDeposit contract
    udc = pathfinding_service.user_deposit_contract
    udc_balance = udc.functions.effectiveBalance(iou.sender).call()
    required_deposit = round(expected_amount * UDC_SECURITY_MARGIN_FACTOR)
    if udc_balance < required_deposit:
        raise exceptions.DepositTooLow(required_deposit=required_deposit)

    # Save latest IOU
    iou.claimed = False
    pathfinding_service.database.upsert_iou(iou)
Пример #9
0
 def upsert_iou(self, iou: IOU) -> None:
     iou_dict = IOU.Schema(strict=True).dump(iou)[0]
     for key in ("amount", "expiration_block"):
         iou_dict[key] = hex256(iou_dict[key])
     self.conn.execute(
         """
         INSERT OR REPLACE INTO iou (
             sender, amount, expiration_block, signature, claimed
         ) VALUES (
             :sender, :amount, :expiration_block, :signature, :claimed
         )
     """,
         iou_dict,
     )
Пример #10
0
    def get(self,
            token_network_address: TokenNetworkAddress) -> Tuple[dict, int]:
        iou_request, errors = IOURequest.Schema().load(request.args)
        if errors:
            raise exceptions.InvalidRequest(**errors)
        if not iou_request.is_signature_valid():
            raise exceptions.InvalidSignature
        if iou_request.timestamp < datetime.utcnow() - MAX_AGE_OF_IOU_REQUESTS:
            raise exceptions.RequestOutdated

        last_iou = self.pathfinding_service.database.get_iou(
            sender=iou_request.sender, claimed=False)
        if last_iou:
            last_iou = IOU.Schema(strict=True,
                                  exclude=["claimed"]).dump(last_iou)[0]
            return {"last_iou": last_iou}, 200

        return {"last_iou": None}, 404
Пример #11
0
def make_iou(sender_priv_key, receiver, amount=1, expiration_block=MIN_IOU_EXPIRY + 100) -> IOU:
    iou_dict = {
        "sender": private_key_to_address(sender_priv_key),
        "receiver": receiver,
        "amount": amount,
        "expiration_block": expiration_block,
    }
    iou_dict["signature"] = encode_hex(
        sign_one_to_n_iou(
            privatekey=sender_priv_key,
            sender=iou_dict["sender"],
            receiver=receiver,
            amount=amount,
            expiration=expiration_block,
        )
    )
    iou = IOU.Schema().load(iou_dict)[0]
    iou.claimed = False
    return iou
Пример #12
0
    def get(
        self,
        token_network_address: str  # pylint: disable=unused-argument
    ) -> Tuple[dict, int]:
        try:
            iou_request = IOURequest.Schema().load(request.args)
        except marshmallow.ValidationError as ex:
            raise exceptions.InvalidRequest(**ex.messages)
        if not iou_request.is_signature_valid():
            raise exceptions.InvalidSignature
        if iou_request.timestamp < datetime.utcnow() - MAX_AGE_OF_IOU_REQUESTS:
            raise exceptions.RequestOutdated

        last_iou = self.pathfinding_service.database.get_iou(
            sender=iou_request.sender, claimed=False)
        if last_iou:
            last_iou = IOU.Schema(exclude=["claimed"]).dump(last_iou)
            return {"last_iou": last_iou}, 200

        return {"last_iou": None}, 404
Пример #13
0
def make_iou(sender_priv_key,
             receiver,
             amount=1,
             expiration_block=MIN_IOU_EXPIRY + 100) -> IOU:
    iou_dict = {
        'sender': private_key_to_address(sender_priv_key),
        'receiver': receiver,
        'amount': amount,
        'expiration_block': expiration_block,
    }
    iou_dict['signature'] = encode_hex(
        sign_one_to_n_iou(
            privatekey=sender_priv_key,
            sender=iou_dict['sender'],
            receiver=receiver,
            amount=amount,
            expiration=expiration_block,
        ))
    iou = IOU.Schema().load(iou_dict)[0]
    iou.claimed = False
    return iou
Пример #14
0
    def get_ious(
        self,
        sender: Optional[Address] = None,
        claimable_until: Optional[Timestamp] = None,
        claimed: Optional[bool] = None,
        claimable_until_after: Optional[Timestamp] = None,
        claimable_until_before: Optional[Timestamp] = None,
        amount_at_least: Optional[TokenAmount] = None,
    ) -> Iterator[IOU]:
        query = """
            SELECT *, (SELECT chain_id FROM blockchain) AS chain_id
            FROM iou
            WHERE 1=1
        """
        args: list = []
        if sender is not None:
            query += " AND sender = ?"
            args.append(to_checksum_address(sender))
        if claimable_until is not None:
            query += " AND claimable_until = ?"
            args.append(hex256(claimable_until))
        if claimed is not None:
            query += " AND claimed = ?"
            args.append(claimed)
        if claimable_until_before is not None:
            query += " AND claimable_until < ?"
            args.append(hex256(claimable_until_before))
        if claimable_until_after is not None:
            query += " AND claimable_until > ?"
            args.append(hex256(claimable_until_after))
        if amount_at_least is not None:
            query += " AND amount >= ?"
            args.append(hex256(amount_at_least))

        with self._cursor() as cursor:
            for row in cursor.execute(query, args):
                iou_dict = dict(zip(row.keys(), row))
                iou_dict["receiver"] = to_checksum_address(self.pfs_address)
                yield IOU.Schema().load(iou_dict)
Пример #15
0
    def get_iou(
        self,
        sender: Address,
        expiration_block: int = None,
        claimed: bool = None,
    ) -> Optional[IOU]:
        query = "SELECT * FROM iou WHERE sender = ?"
        args: list = [sender]
        if expiration_block is not None:
            query += " AND expiration_block = ?"
            args.append(expiration_block)
        if claimed is not None:
            query += " AND claimed = ?"
            args.append(claimed)

        row = self.conn.execute(query, args).fetchone()
        if row is None:
            return None

        iou_dict = dict(zip(row.keys(), row))
        iou_dict['receiver'] = self.pfs_address
        return IOU.Schema().load(iou_dict)[0]
Пример #16
0
 def f(
         sender_priv_key: PrivateKey,
         receiver: Address,
         amount=1,
         claimable_until=1000000000 * 15 + MIN_IOU_EXPIRY,
         one_to_n_address: Address = one_to_n_contract_address,
         chain_id: ChainID = ChainID(61),
 ) -> IOU:
     receiver_hex: str = to_checksum_address(receiver)
     iou_dict = {
         "sender":
         to_checksum_address(private_key_to_address(sender_priv_key)),
         "receiver": receiver_hex,
         "amount": amount,
         "claimable_until": claimable_until,
         "one_to_n_address": to_checksum_address(one_to_n_address),
         "chain_id": chain_id,
     }
     iou_dict["signature"] = encode_hex(
         sign_one_to_n_iou(privatekey=sender_priv_key, **iou_dict))
     iou = IOU.Schema().load(iou_dict)
     iou.claimed = False
     return iou
Пример #17
0
 def f(
     sender_priv_key,
     receiver: Address,
     amount=1,
     expiration_block=MIN_IOU_EXPIRY + 100,
     one_to_n_address=one_to_n_contract.address,
     chain_id=1,
 ) -> IOU:
     receiver_hex: str = to_checksum_address(receiver)
     iou_dict = {
         "sender":
         to_checksum_address(private_key_to_address(sender_priv_key)),
         "receiver": receiver_hex,
         "amount": amount,
         "expiration_block": expiration_block,
         "one_to_n_address": to_checksum_address(one_to_n_address),
         "chain_id": chain_id,
     }
     iou_dict["signature"] = encode_hex(
         sign_one_to_n_iou(privatekey=sender_priv_key, **iou_dict))
     iou = IOU.Schema().load(iou_dict)
     iou.claimed = False
     return iou