예제 #1
0
def test_metrics_iou(  # pylint: disable=too-many-locals
    pathfinding_service_web3_mock: PathfindingService,
    one_to_n_contract,
    web3: Web3,
    deposit_to_udc,
    get_accounts,
    get_private_key,
):
    pfs = pathfinding_service_web3_mock

    metrics_state = save_metrics_state(metrics.REGISTRY)
    # Prepare test data
    account = [decode_hex(acc) for acc in get_accounts(1)][0]
    local_signer = LocalSigner(private_key=get_private_key(account))
    iou = IOU(
        sender=account,
        receiver=pfs.address,
        amount=TokenAmount(100),
        expiration_block=BlockNumber(100),
        signature=Signature(bytes([1] * 64)),
        chain_id=ChainID(61),
        one_to_n_address=to_canonical_address(one_to_n_contract.address),
        claimed=False,
    )
    iou.signature = Signature(local_signer.sign(iou.packed_data()))
    pfs.database.upsert_iou(iou)
    deposit_to_udc(iou.sender, 300)

    # Claim IOUs
    skipped, failures = claim_ious(
        ious=[iou],
        claim_cost_rdn=TokenAmount(100),
        one_to_n_contract=one_to_n_contract,
        web3=web3,
        database=pfs.database,
    )
    assert (skipped, failures) == (0, 0)

    assert (
        metrics_state.get_delta(
            "economics_iou_claims_total", labels=metrics.IouStatus.SUCCESSFUL.to_label_dict()
        )
        == 1.0
    )
    assert (
        metrics_state.get_delta(
            "economics_iou_claims_token_total",
            labels=metrics.IouStatus.SUCCESSFUL.to_label_dict(),
        )
        == 100.0
    )
예제 #2
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)
예제 #3
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)
예제 #4
0
def test_get_ious_via_debug_endpoint(
    api_sut_with_debug: ServiceApi, api_url: str, addresses: List[Address]
):
    hex_addrs = [to_checksum_address(addr) for addr in addresses]
    iou = IOU(
        sender=addresses[0],
        receiver=addresses[4],
        amount=TokenAmount(111),
        expiration_block=BlockNumber(7619644),
        signature=Signature(
            decode_hex("118a93e9fd0a3a1c3d6edbad194b5c9d95715c754881d80e23e985793b1e13de")
        ),
        claimed=False,
        chain_id=ChainID(1),
        one_to_n_address=api_sut_with_debug.one_to_n_address,
    )
    api_sut_with_debug.pathfinding_service.database.upsert_iou(iou)

    # now there must be an iou debug endpoint for a request of a sender in the database
    url_iou_debug = api_url + f"/_debug/ious/{hex_addrs[0]}"
    response_debug = requests.get(url_iou_debug)
    assert response_debug.status_code == 200
    response_iou = response_debug.json()
    assert response_iou == {"sender": hex_addrs[0], "amount": 111, "expiration_block": 7619644}

    # but there is no iou debug endpoint for a request of a sender not in the database
    url_iou_debug = api_url + f"/_debug/ious/{hex_addrs[1]}"
    response_debug = requests.get(url_iou_debug)
    assert response_debug.status_code == 200
    ious = response_debug.json()
    assert ious == {}
예제 #5
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)
예제 #6
0
def process_payment(iou: IOU, pathfinding_service: PathfindingService) -> None:
    if pathfinding_service.service_fee == 0:
        return
    if iou is None:
        raise exceptions.MissingIOU

    # Basic IOU validity checks
    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)
예제 #7
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
예제 #8
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
예제 #9
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
예제 #10
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)])
예제 #11
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,
     )
예제 #12
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
예제 #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(
        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
예제 #15
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
예제 #16
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]
예제 #17
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)
예제 #18
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
예제 #19
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
예제 #20
0
def test_claim_fees(  # pylint: disable=too-many-locals
    pathfinding_service_mock,
    one_to_n_contract,
    web3,
    deposit_to_udc,
    get_accounts,
    get_private_key,
):
    # Prepare test data
    accounts = [decode_hex(acc) for acc in get_accounts(6)]
    pfs = pathfinding_service_mock
    iou_inputs: List[dict] = [
        dict(sender=accounts[0], amount=100, deposit=200),
        dict(sender=accounts[1], amount=200, deposit=100),
        dict(sender=accounts[2], amount=102,
             deposit=0),  # insufficient deposit
        dict(sender=accounts[3], amount=103,
             deposit=99),  # insufficient deposit
        dict(sender=accounts[4], amount=104, claimed=True),  # already claimed
        dict(sender=accounts[4], amount=99),  # too low amount
        dict(sender=accounts[5], expiration_block=1000,
             amount=104),  # does not expire, yet
    ]

    # Create IOUs from `iou_inputs`
    ious: List[IOU] = []
    for iou_dict in iou_inputs:
        local_signer = LocalSigner(
            private_key=decode_hex(get_private_key(iou_dict["sender"])))
        iou = IOU(
            sender=iou_dict["sender"],
            receiver=pfs.address,
            amount=TokenAmount(iou_dict["amount"]),
            expiration_block=BlockNumber(iou_dict.get("expiration_block",
                                                      100)),
            signature=Signature(bytes([1] * 64)),  # dummy, replaced below
            chain_id=ChainID(1),
            one_to_n_address=decode_hex(one_to_n_contract.address),
            claimed=iou_dict.get("claimed", False),
        )
        iou.signature = Signature(local_signer.sign(iou.packed_data()))
        ious.append(iou)
        pfs.database.upsert_iou(iou)
        if iou_dict.get("deposit", 0) > 0:
            deposit_to_udc(iou.sender, iou_dict["deposit"])

    # Check if the right IOUs are considered to be claimable
    expected_claimable = ious[:4]
    claimable_ious = list(
        get_claimable_ious(pfs.database,
                           expires_before=BlockNumber(1000),
                           claim_cost_rdn=TokenAmount(100)))
    assert claimable_ious == expected_claimable

    # Claim IOUs
    skipped, failures = claim_ious(
        claimable_ious,
        claim_cost_rdn=TokenAmount(100),
        one_to_n_contract=one_to_n_contract,
        web3=web3,
        database=pfs.database,
    )
    assert (skipped, failures) == (2, 0)

    # Those IOUs which have enough deposit should be marked as claimed
    # * in the blockchain
    # * in the database
    # All other IOUs must not be changed.
    claimable_with_enough_deposit = ious[:2]
    for iou in ious:
        expected_claimed = iou in claimable_with_enough_deposit

        iou_in_db = pfs.database.get_iou(sender=iou.sender,
                                         expiration_block=iou.expiration_block)
        assert iou_in_db.claimed == expected_claimed

        is_settled = bool(
            one_to_n_contract.functions.settled_sessions(
                iou.session_id).call())
        assert is_settled == expected_claimed
def test_claim_fees(  # pylint: disable=too-many-locals
    pathfinding_service_web3_mock: PathfindingService,
    one_to_n_contract,
    web3: Web3,
    deposit_to_udc,
    get_accounts,
    get_private_key,
):
    pfs = pathfinding_service_web3_mock

    # Prepare test data
    accounts = [decode_hex(acc) for acc in get_accounts(7)]
    iou_inputs: List[dict] = [
        dict(sender=accounts[0], amount=100, deposit=200),
        dict(sender=accounts[1], amount=200, deposit=100),
        dict(sender=accounts[2], amount=102,
             deposit=0),  # insufficient deposit
        dict(sender=accounts[3], amount=103,
             deposit=99),  # insufficient deposit
        dict(sender=accounts[4], amount=104, claimed=True),  # already claimed
        dict(sender=accounts[4], amount=99),  # too low amount
        dict(sender=accounts[5], claimable_until=100 * 15,
             amount=104),  # does not expire, yet
        dict(
            sender=accounts[6],
            claimable_until=web3.eth.get_block(web3.eth.block_number -
                                               1).timestamp,  # type: ignore
            amount=104,
        ),  # already expired
    ]

    # Create IOUs from `iou_inputs`
    ious: List[IOU] = []
    for iou_dict in iou_inputs:
        local_signer = LocalSigner(
            private_key=get_private_key(iou_dict["sender"]))
        iou = IOU(
            sender=iou_dict["sender"],
            receiver=pfs.address,
            amount=TokenAmount(iou_dict["amount"]),
            claimable_until=iou_dict.get(
                "claimable_until",
                web3.eth.get_block("latest").timestamp + 100  # type: ignore
            ),
            signature=Signature(bytes([1] * 64)),  # dummy, replaced below
            chain_id=ChainID(61),
            one_to_n_address=to_canonical_address(one_to_n_contract.address),
            claimed=iou_dict.get("claimed", False),
        )
        iou.signature = Signature(local_signer.sign(iou.packed_data()))
        ious.append(iou)
        pfs.database.upsert_iou(iou)
        if iou_dict.get("deposit", 0) > 0:
            deposit_to_udc(iou.sender, iou_dict["deposit"])

    # Check if the right IOUs are considered to be claimable
    expected_claimable = ious[:4]
    timestamp_now = web3.eth.get_block("latest").timestamp  # type: ignore

    claimable_ious = list(
        get_claimable_ious(
            database=pfs.database,
            claimable_until_after=timestamp_now,
            claimable_until_before=timestamp_now +
            10000,  # TODO: use proper boundaries
            claim_cost_rdn=TokenAmount(100),
        ))
    assert claimable_ious == expected_claimable

    # Claim IOUs
    skipped, failures = claim_ious(
        ious=claimable_ious,
        claim_cost_rdn=TokenAmount(100),
        one_to_n_contract=one_to_n_contract,
        web3=web3,
        database=pfs.database,
    )
    assert (skipped, failures) == (2, 0)

    # Those IOUs which have enough deposit should be marked as claimed
    # * in the blockchain
    # * in the database
    # All other IOUs must not be changed.
    claimable_with_enough_deposit = ious[:2]
    for iou in ious:
        expected_claimed = iou in claimable_with_enough_deposit

        iou_in_db = pfs.database.get_iou(sender=iou.sender,
                                         claimable_until=iou.claimable_until)
        assert iou_in_db
        assert iou_in_db.claimed == expected_claimed

        is_settled = bool(
            one_to_n_contract.functions.settled_sessions(
                iou.session_id).call())
        assert is_settled == expected_claimed