コード例 #1
0
def test_store_user(user_db_manager):
    # Tests that users can be properly stored in the database

    # Store user should work as long as the user_pk is properly formatted and data is a dictionary
    user_id = "02" + get_random_value_hex(32)
    user_info = UserInfo(available_slots=42, subscription_expiry=100)

    assert user_db_manager.store_user(user_id, user_info.to_dict()) is True
コード例 #2
0
def test_add_appointment(watcher, generate_dummy_appointment):
    # Simulate the user is registered
    user_sk, user_pk = generate_keypair()
    available_slots = 100
    user_id = Cryptographer.get_compressed_pk(user_pk)
    watcher.gatekeeper.registered_users[user_id] = UserInfo(
        available_slots=available_slots,
        subscription_expiry=watcher.block_processor.get_block_count() + 1)

    appointment, dispute_tx = generate_dummy_appointment()
    appointment_signature = Cryptographer.sign(appointment.serialize(),
                                               user_sk)

    response = watcher.add_appointment(appointment, appointment_signature)
    assert response.get("locator") == appointment.locator
    assert Cryptographer.get_compressed_pk(
        watcher.signing_key.public_key) == Cryptographer.get_compressed_pk(
            Cryptographer.recover_pk(
                receipts.create_appointment_receipt(
                    appointment_signature, response.get("start_block")),
                response.get("signature"),
            ))
    assert response.get("available_slots") == available_slots - 1

    # Check that we can also add an already added appointment (same locator)
    response = watcher.add_appointment(appointment, appointment_signature)
    assert response.get("locator") == appointment.locator
    assert Cryptographer.get_compressed_pk(
        watcher.signing_key.public_key) == Cryptographer.get_compressed_pk(
            Cryptographer.recover_pk(
                receipts.create_appointment_receipt(
                    appointment_signature, response.get("start_block")),
                response.get("signature"),
            ))
    # The slot count should not have been reduced and only one copy is kept.
    assert response.get("available_slots") == available_slots - 1
    assert len(watcher.locator_uuid_map[appointment.locator]) == 1

    # If two appointments with the same locator come from different users, they are kept.
    another_user_sk, another_user_pk = generate_keypair()
    another_user_id = Cryptographer.get_compressed_pk(another_user_pk)
    watcher.gatekeeper.registered_users[another_user_id] = UserInfo(
        available_slots=available_slots,
        subscription_expiry=watcher.block_processor.get_block_count() + 1)

    appointment_signature = Cryptographer.sign(appointment.serialize(),
                                               another_user_sk)
    response = watcher.add_appointment(appointment, appointment_signature)
    assert response.get("locator") == appointment.locator
    assert Cryptographer.get_compressed_pk(
        watcher.signing_key.public_key) == Cryptographer.get_compressed_pk(
            Cryptographer.recover_pk(
                receipts.create_appointment_receipt(
                    appointment_signature, response.get("start_block")),
                response.get("signature"),
            ))
    assert response.get("available_slots") == available_slots - 1
    assert len(watcher.locator_uuid_map[appointment.locator]) == 2
コード例 #3
0
def test_load_user(user_db_manager):
    # Tests that loading a user should work, as long as the user is there

    # Add the user first
    user_id = "02" + get_random_value_hex(32)
    user_info = UserInfo(available_slots=42, subscription_expiry=100)
    user_db_manager.store_user(user_id, user_info.to_dict())

    # Now load it
    assert user_db_manager.load_user(user_id) == user_info.to_dict()
コード例 #4
0
def test_user_info_from_dict_wrong_data():
    # If any of the dictionary fields is missing, building from dict will fail
    d1 = {"available_slots": "", "subscription_expiry": ""}
    d2 = {"available_slots": "", "appointments": ""}
    d3 = {"subscription_expiry": "", "appointments": ""}

    for d in [d1, d2, d3]:
        with pytest.raises(
                ValueError,
                match="Wrong appointment data, some fields are missing"):
            UserInfo.from_dict(d)
コード例 #5
0
def test_store_user_wrong(user_db_manager):
    # Tests that trying to store wrong data will fail

    # Wrong pks should return False on adding
    user_id = "04" + get_random_value_hex(32)
    user_info = UserInfo(available_slots=42, subscription_expiry=100)
    assert user_db_manager.store_user(user_id, user_info.to_dict()) is False

    # Same for wrong types
    assert user_db_manager.store_user(42, user_info.to_dict()) is False

    # And for wrong type user data
    assert user_db_manager.store_user(user_id, 42) is False
コード例 #6
0
def test_add_appointment_in_cache(watcher):
    # Generate an appointment and add the dispute txid to the cache
    user_sk, user_pk = generate_keypair()
    user_id = Cryptographer.get_compressed_pk(user_pk)
    watcher.gatekeeper.registered_users[user_id] = UserInfo(available_slots=1, subscription_expiry=10)

    appointment, dispute_tx = generate_dummy_appointment()
    dispute_txid = watcher.block_processor.decode_raw_transaction(dispute_tx).get("txid")
    watcher.locator_cache.cache[appointment.locator] = dispute_txid

    # Try to add the appointment
    response = watcher.add_appointment(appointment, Cryptographer.sign(appointment.serialize(), user_sk))

    # The appointment is accepted but it's not in the Watcher
    assert (
        response
        and response.get("locator") == appointment.locator
        and Cryptographer.get_compressed_pk(watcher.signing_key.public_key)
        == Cryptographer.get_compressed_pk(Cryptographer.recover_pk(appointment.serialize(), response.get("signature")))
    )
    assert not watcher.locator_uuid_map.get(appointment.locator)

    # It went to the Responder straightaway
    assert appointment.locator in [tracker.get("locator") for tracker in watcher.responder.trackers.values()]

    # Trying to send it again should fail since it is already in the Responder
    with pytest.raises(AppointmentAlreadyTriggered):
        watcher.add_appointment(appointment, Cryptographer.sign(appointment.serialize(), user_sk))
コード例 #7
0
def test_add_appointment_multiple_times_same_user(internal_api,
                                                  client,
                                                  appointment,
                                                  block_processor,
                                                  n=MULTIPLE_APPOINTMENTS):
    # Multiple appointments with the same locator should be valid and count as updates
    appointment_signature = Cryptographer.sign(appointment.serialize(),
                                               user_sk)

    # Simulate registering enough slots (end time does not matter here)
    internal_api.watcher.gatekeeper.registered_users[user_id] = UserInfo(
        available_slots=n,
        subscription_expiry=block_processor.get_block_count() + 1)
    for _ in range(n):
        r = add_appointment(
            client, {
                "appointment": appointment.to_dict(),
                "signature": appointment_signature
            }, user_id)
        assert r.status_code == HTTP_OK
        assert r.json.get("available_slots") == n - 1
        assert r.json.get("start_block") == block_processor.get_block_count()

    # Since all updates came from the same user, only the last one is stored
    assert len(internal_api.watcher.locator_uuid_map[appointment.locator]) == 1
コード例 #8
0
def test_add_appointment_in_cache_invalid_transaction(
        watcher, generate_dummy_appointment):
    # Generate an appointment that cannot be decrypted and add the dispute txid to the cache
    user_sk, user_pk = generate_keypair()
    user_id = Cryptographer.get_compressed_pk(user_pk)
    watcher.gatekeeper.registered_users[user_id] = UserInfo(
        available_slots=1,
        subscription_expiry=watcher.block_processor.get_block_count() + 1)

    appointment, dispute_tx = generate_dummy_appointment()
    appointment.encrypted_blob = appointment.encrypted_blob[::-1]
    dispute_txid = watcher.block_processor.decode_raw_transaction(
        dispute_tx).get("txid")
    watcher.locator_cache.cache[appointment.locator] = dispute_txid

    # Try to add the appointment
    user_signature = Cryptographer.sign(appointment.serialize(), user_sk)
    response = watcher.add_appointment(appointment, user_signature)
    appointment_receipt = receipts.create_appointment_receipt(
        user_signature, response.get("start_block"))

    # The appointment is accepted but dropped (same as an invalid appointment that gets triggered)
    assert (response and response.get("locator") == appointment.locator
            and Cryptographer.get_compressed_pk(watcher.signing_key.public_key)
            == Cryptographer.get_compressed_pk(
                Cryptographer.recover_pk(appointment_receipt,
                                         response.get("signature"))))

    assert not watcher.locator_uuid_map.get(appointment.locator)
    assert appointment.locator not in [
        tracker.get("locator")
        for tracker in watcher.responder.trackers.values()
    ]
コード例 #9
0
def test_delete_outdated_users(gatekeeper):
    # This tests the deletion of users whose subscription has outdated (subscription expires now)

    # Create some users with associated data and add them to the gatekeeper
    users = {}
    current_height = gatekeeper.block_processor.get_block_count()
    for _ in range(10):
        appointments = {
            get_random_value_hex(32):
            Appointment(get_random_value_hex(32), None, None)
        }
        user_id = get_random_value_hex(16)
        user_info = UserInfo(available_slots=100,
                             subscription_expiry=current_height,
                             appointments=appointments)

        users[user_id] = user_info
        gatekeeper.registered_users[user_id] = user_info

    # Get a list of the users that should be deleted at this block height (must match the newly generated ones)
    users_to_be_deleted = gatekeeper.get_outdated_user_ids(
        current_height + gatekeeper.expiry_delta)
    assert users_to_be_deleted == list(users.keys())

    # Delete the users
    Cleaner.delete_outdated_users(users_to_be_deleted,
                                  gatekeeper.registered_users,
                                  gatekeeper.user_db)

    # Check that the users are not in the gatekeeper anymore
    for user_id in users_to_be_deleted:
        assert user_id not in gatekeeper.registered_users
        assert not gatekeeper.user_db.load_user(user_id)
コード例 #10
0
def test_delete_gatekeeper_appointments(gatekeeper):
    # delete_gatekeeper_appointments should delete the appointments from user as long as both exist

    appointments_not_to_delete = {}
    appointments_to_delete = {}
    # Let's add some users and appointments to the Gatekeeper
    for _ in range(10):
        user_id = get_random_value_hex(16)
        # The UserInfo params do not matter much here
        gatekeeper.registered_users[user_id] = UserInfo(available_slots=100,
                                                        subscription_expiry=0)
        for _ in range(random.randint(0, 10)):
            # Add some appointments
            uuid = get_random_value_hex(16)
            gatekeeper.registered_users[user_id].appointments[uuid] = 1

            if random.randint(0, 1) % 2:
                appointments_to_delete[uuid] = user_id
            else:
                appointments_not_to_delete[uuid] = user_id

    # Now let's delete half of them
    Cleaner.delete_gatekeeper_appointments(gatekeeper, appointments_to_delete)

    all_appointments_gatekeeper = []
    # Let's get all the appointments in the Gatekeeper
    for user_id, user in gatekeeper.registered_users.items():
        all_appointments_gatekeeper.extend(user.appointments)

    # Check that the first half of the appointments are not in the Gatekeeper, but the second half is
    assert not set(appointments_to_delete).issubset(
        all_appointments_gatekeeper)
    assert set(appointments_not_to_delete).issubset(
        all_appointments_gatekeeper)
コード例 #11
0
def test_filter_valid_breaches(watcher, generate_dummy_appointment_w_trigger,
                               monkeypatch):
    # filter_breaches returns computes two collections, one with the valid breaches (breaches that properly decrypt
    # appointments) and one with invalid ones. Test it with a single valid breach.

    # Create a new appointment
    dummy_appointment, dispute_txid = generate_dummy_appointment_w_trigger()

    # Mock the interaction with the Gatekeeper. We'll the appointment to the Watcher instead of mocking all the
    # structures + db
    expiry = 100
    user_info = UserInfo(MAX_APPOINTMENTS, expiry)
    monkeypatch.setattr(watcher.gatekeeper, "authenticate_user",
                        lambda x, y: user_id)
    monkeypatch.setattr(watcher.gatekeeper, "has_subscription_expired",
                        lambda x: (False, expiry))
    monkeypatch.setattr(watcher.gatekeeper, "get_user_info",
                        lambda x: user_info)
    watcher.add_appointment(dummy_appointment,
                            dummy_appointment.user_signature)

    # Filter the data and check
    potential_breaches = {dummy_appointment.locator: dispute_txid}
    valid_breaches, invalid_breaches = watcher.filter_breaches(
        potential_breaches)

    # We have "triggered" a single breach and it was valid.
    assert len(invalid_breaches) == 0 and len(valid_breaches) == 1
コード例 #12
0
def test_add_appointment_multiple_times_different_users(
        internal_api,
        client,
        appointment,
        block_processor,
        n=MULTIPLE_APPOINTMENTS):
    # If the same appointment comes from different users, all are kept
    # Create user keys and appointment signatures
    user_keys = [generate_keypair() for _ in range(n)]
    signatures = [
        Cryptographer.sign(appointment.serialize(), key[0])
        for key in user_keys
    ]
    tmp_user_ids = [Cryptographer.get_compressed_pk(pk) for _, pk in user_keys]

    # Add one slot per public key
    for pair in user_keys:
        user_id = Cryptographer.get_compressed_pk(pair[1])
        internal_api.watcher.gatekeeper.registered_users[user_id] = UserInfo(
            available_slots=1,
            subscription_expiry=block_processor.get_block_count() + 1)

    # Send the appointments
    for compressed_pk, signature in zip(tmp_user_ids, signatures):
        r = add_appointment(client, {
            "appointment": appointment.to_dict(),
            "signature": signature
        }, compressed_pk)
        assert r.status_code == HTTP_OK
        assert r.json.get("available_slots") == 0
        assert r.json.get("start_block") == block_processor.get_block_count()

    # Check that all the appointments have been added and that there are no duplicates
    assert len(set(
        internal_api.watcher.locator_uuid_map[appointment.locator])) == n
コード例 #13
0
def test_add_too_many_appointments(watcher, generate_dummy_appointment,
                                   monkeypatch):
    # Adding appointment beyond the user limit should fail

    # Mock the user being registered
    expiry = 100
    user_info = UserInfo(MAX_APPOINTMENTS, expiry)
    monkeypatch.setattr(watcher.gatekeeper, "authenticate_user",
                        lambda x, y: user_id)
    monkeypatch.setattr(watcher.gatekeeper, "has_subscription_expired",
                        lambda x: (False, expiry))
    monkeypatch.setattr(watcher.gatekeeper, "get_user_info",
                        lambda x: user_info)

    for i in range(user_info.available_slots):
        appointment = generate_dummy_appointment()
        response = watcher.add_appointment(appointment,
                                           appointment.user_signature)
        appointment_receipt = receipts.create_appointment_receipt(
            appointment.user_signature, response.get("start_block"))

        assert response.get("locator") == appointment.locator
        assert Cryptographer.get_compressed_pk(
            watcher.signing_key.public_key) == Cryptographer.get_compressed_pk(
                Cryptographer.recover_pk(appointment_receipt,
                                         response.get("signature")))

    with pytest.raises(AppointmentLimitReached):
        appointment = generate_dummy_appointment()
        appointment_signature = Cryptographer.sign(appointment.serialize(),
                                                   user_sk)
        watcher.add_appointment(appointment, appointment_signature)
コード例 #14
0
def test_add_too_many_appointments(watcher):
    # Simulate the user is registered
    user_sk, user_pk = generate_keypair()
    available_slots = 100
    user_id = Cryptographer.get_compressed_pk(user_pk)
    watcher.gatekeeper.registered_users[user_id] = UserInfo(available_slots=available_slots, subscription_expiry=10)

    # Appointments on top of the limit should be rejected
    watcher.appointments = dict()

    for i in range(MAX_APPOINTMENTS):
        appointment, dispute_tx = generate_dummy_appointment()
        appointment_signature = Cryptographer.sign(appointment.serialize(), user_sk)

        response = watcher.add_appointment(appointment, appointment_signature)
        assert response.get("locator") == appointment.locator
        assert Cryptographer.get_compressed_pk(watcher.signing_key.public_key) == Cryptographer.get_compressed_pk(
            Cryptographer.recover_pk(appointment.serialize(), response.get("signature"))
        )
        assert response.get("available_slots") == available_slots - (i + 1)

    with pytest.raises(AppointmentLimitReached):
        appointment, dispute_tx = generate_dummy_appointment()
        appointment_signature = Cryptographer.sign(appointment.serialize(), user_sk)
        watcher.add_appointment(appointment, appointment_signature)
コード例 #15
0
def test_add_too_many_appointment(internal_api, client, block_processor,
                                  generate_dummy_appointment):
    # Give slots to the user
    internal_api.watcher.gatekeeper.registered_users[user_id] = UserInfo(
        available_slots=200,
        subscription_expiry=block_processor.get_block_count() + 1)

    free_appointment_slots = MAX_APPOINTMENTS - len(
        internal_api.watcher.appointments)

    for i in range(free_appointment_slots + 1):
        appointment, dispute_tx = generate_dummy_appointment()
        locator_dispute_tx_map[appointment.locator] = dispute_tx

        appointment_signature = Cryptographer.sign(appointment.serialize(),
                                                   user_sk)
        r = add_appointment(
            client, {
                "appointment": appointment.to_dict(),
                "signature": appointment_signature
            }, user_id)

        if i < free_appointment_slots:
            assert r.status_code == HTTP_OK and r.json.get(
                "start_block") == block_processor.get_block_count()
        else:
            assert r.status_code == HTTP_SERVICE_UNAVAILABLE
コード例 #16
0
def test_add_appointment_update_smaller(internal_api, client, appointment,
                                        block_processor):
    # Update an appointment by one bigger, and check slots are freed
    internal_api.watcher.gatekeeper.registered_users[user_id] = UserInfo(
        available_slots=2,
        subscription_expiry=block_processor.get_block_count() + 1)
    # This should take 2 slots
    appointment.encrypted_blob = TWO_SLOTS_BLOTS
    appointment_signature = Cryptographer.sign(appointment.serialize(),
                                               user_sk)
    r = add_appointment(client, {
        "appointment": appointment.to_dict(),
        "signature": appointment_signature
    }, user_id)
    assert (r.status_code == HTTP_OK and r.json.get("available_slots") == 0
            and r.json.get("start_block") == block_processor.get_block_count())

    # Let's update with one just small enough
    appointment.encrypted_blob = "A" * (ENCRYPTED_BLOB_MAX_SIZE_HEX - 2)
    appointment_signature = Cryptographer.sign(appointment.serialize(),
                                               user_sk)
    r = add_appointment(client, {
        "appointment": appointment.to_dict(),
        "signature": appointment_signature
    }, user_id)
    assert (r.status_code == HTTP_OK and r.json.get("available_slots") == 1
            and r.json.get("start_block") == block_processor.get_block_count())
コード例 #17
0
def test_add_appointment_update_same_size(internal_api, client, appointment,
                                          block_processor):
    # Update an appointment by one of the same size and check that no additional slots are filled
    internal_api.watcher.gatekeeper.registered_users[user_id] = UserInfo(
        available_slots=1,
        subscription_expiry=block_processor.get_block_count() + 1)

    appointment_signature = Cryptographer.sign(appointment.serialize(),
                                               user_sk)
    r = add_appointment(client, {
        "appointment": appointment.to_dict(),
        "signature": appointment_signature
    }, user_id)
    assert (r.status_code == HTTP_OK and r.json.get("available_slots") == 0
            and r.json.get("start_block") == block_processor.get_block_count())

    # The user has no additional slots, but it should be able to update
    # Let's just reverse the encrypted blob for example
    appointment.encrypted_blob = appointment.encrypted_blob[::-1]
    appointment_signature = Cryptographer.sign(appointment.serialize(),
                                               user_sk)
    r = add_appointment(client, {
        "appointment": appointment.to_dict(),
        "signature": appointment_signature
    }, user_id)
    assert (r.status_code == HTTP_OK and r.json.get("available_slots") == 0
            and r.json.get("start_block") == block_processor.get_block_count())
コード例 #18
0
def test_add_appointment_in_cache_invalid_transaction(api, client):
    api.watcher.gatekeeper.registered_users[user_id] = UserInfo(available_slots=1, subscription_expiry=0)

    # We need to create the appointment manually
    dispute_tx = create_dummy_transaction()
    dispute_txid = dispute_tx.tx_id.hex()
    penalty_tx = create_dummy_transaction(dispute_txid)

    locator = compute_locator(dispute_txid)
    dummy_appointment_data = {"tx": penalty_tx.hex(), "tx_id": dispute_txid, "to_self_delay": 20}
    encrypted_blob = Cryptographer.encrypt(dummy_appointment_data.get("tx")[::-1], dummy_appointment_data.get("tx_id"))

    appointment_data = {
        "locator": locator,
        "to_self_delay": dummy_appointment_data.get("to_self_delay"),
        "encrypted_blob": encrypted_blob,
        "user_id": get_random_value_hex(16),
    }

    appointment = ExtendedAppointment.from_dict(appointment_data)
    api.watcher.locator_cache.cache[appointment.locator] = dispute_tx.tx_id.hex()
    appointment_signature = Cryptographer.sign(appointment.serialize(), user_sk)

    # Add the data to the cache
    api.watcher.locator_cache.cache[dispute_txid] = appointment.locator

    # The appointment should be accepted
    r = add_appointment(client, {"appointment": appointment.to_dict(), "signature": appointment_signature}, user_id)
    assert (
        r.status_code == HTTP_OK
        and r.json.get("available_slots") == 0
        and r.json.get("start_block") == api.watcher.last_known_block
    )
コード例 #19
0
def test_get_user(internal_api, stub, monkeypatch):
    # it doesn't matter they are not valid user ids and user data object for this test
    mock_user_id = "02c73bad28b78dd7e3bcad609d330e0d60b97fa0e08ca1cf486cb6cab8dd6140ac"
    mock_available_slots = 100
    mock_subscription_expiry = 1234
    mock_user_info = UserInfo(mock_available_slots, mock_subscription_expiry)

    def mock_get_user_info(user_id):
        if user_id == mock_user_id:
            return mock_user_info
        else:
            raise RuntimeError(f"called with an unexpected user_id: {user_id}")

    monkeypatch.setattr(internal_api.watcher, "get_user_info",
                        mock_get_user_info)

    response = stub.get_user(GetUserRequest(user_id=mock_user_id))
    assert isinstance(response, GetUserResponse)

    # FIXME: numbers are currently returned as floats, even if they are integers
    assert json_format.MessageToDict(response.user) == {
        "appointments": [],
        "available_slots": float(mock_available_slots),
        "subscription_expiry": float(mock_subscription_expiry),
    }
コード例 #20
0
def test_update_outdated_users_cache(gatekeeper):
    # update_outdated_users_cache is used to add new entries to the cache and prune old ones if it grows beyond the
    # limit. In normal conditions (no reorg) the method is called once per block height, meaning that the data won't be
    # in the cache when called and it will be afterwards
    current_block_height = gatekeeper.block_processor.get_block_count()
    appointments = {get_random_value_hex(32): 1 for _ in range(10)}
    user_info = UserInfo(available_slots=1,
                         subscription_expiry=current_block_height + 42,
                         appointments=appointments)
    user_id = get_random_value_hex(32)
    gatekeeper.registered_users[user_id] = user_info

    # Check that the entry is not in the cache
    target_height = current_block_height + gatekeeper.expiry_delta + 42
    assert target_height not in gatekeeper.outdated_users_cache

    # Update the cache and check
    gatekeeper.update_outdated_users_cache(target_height)
    cache_entry = gatekeeper.outdated_users_cache.get(target_height)
    # Values is not meant to be used straightaway for checking, but we can flatten it to check it matches
    flattened_appointments = [
        uuid for user_appointments in cache_entry.values()
        for uuid in user_appointments
    ]
    assert list(cache_entry.keys()) == [
        user_id
    ] and flattened_appointments == list(appointments.keys())
コード例 #21
0
def test_update_outdated_users_cache_remove_data(gatekeeper):
    # Tests how the oldest piece of data is removed after OUTDATED_USERS_CACHE_SIZE_BLOCKS
    current_block_height = gatekeeper.block_processor.get_block_count()

    # Add users that are expiring from the current block to OUTDATED_USERS_CACHE_SIZE_BLOCKS -1 and fill the cache with
    # them
    data = {}
    for i in range(OUTDATED_USERS_CACHE_SIZE_BLOCKS):
        appointments = {get_random_value_hex(32): 1 for _ in range(10)}
        user_info = UserInfo(available_slots=1,
                             subscription_expiry=current_block_height + i,
                             appointments=appointments)
        user_id = get_random_value_hex(32)
        gatekeeper.registered_users[user_id] = user_info

        target_block = current_block_height + gatekeeper.expiry_delta + i
        gatekeeper.update_outdated_users_cache(target_block)
        # Create a local version of the expected data to compare {block_id: {user_id: [appointment_uuids]}, ...}
        data[target_block] = {user_id: list(appointments.keys())}

    # Check that the cache is full and that each position matches
    assert len(
        gatekeeper.outdated_users_cache) == OUTDATED_USERS_CACHE_SIZE_BLOCKS
    assert gatekeeper.outdated_users_cache == data

    # Add more blocks and check what data gets kicked (data has an offset of OUTDATED_USERS_CACHE_SIZE_BLOCKS, so we can
    # check if the previous key is there easily)
    for i in range(OUTDATED_USERS_CACHE_SIZE_BLOCKS):
        target_block = current_block_height + gatekeeper.expiry_delta + OUTDATED_USERS_CACHE_SIZE_BLOCKS + i
        assert target_block - OUTDATED_USERS_CACHE_SIZE_BLOCKS in gatekeeper.outdated_users_cache
        gatekeeper.update_outdated_users_cache(target_block)
        assert target_block - OUTDATED_USERS_CACHE_SIZE_BLOCKS not in gatekeeper.outdated_users_cache
コード例 #22
0
def test_get_outdated_appointments(gatekeeper):
    # get_outdated_appointments returns a list of appointment uuids being outdated a a given height

    appointment = {}
    # Let's simulate adding some users with dummy expiry times
    gatekeeper.registered_users = {}
    for i in range(100):
        # Add more than one user expiring at the same time to check it works for multiple users
        for _ in range(2):
            uuid = get_random_value_hex(16)
            user_appointments = {
                get_random_value_hex(16): 1
                for _ in range(10)
            }
            # Add a single appointment to the user
            gatekeeper.registered_users[uuid] = UserInfo(
                100, i, user_appointments)

            if i in appointment:
                appointment[i].update(user_appointments)
            else:
                appointment[i] = deepcopy(user_appointments)

    # Now let's check that the appointments are outdated a the proper time
    for i in range(100):
        assert gatekeeper.get_outdated_appointments(
            i + gatekeeper.expiry_delta) == list(appointment[i].keys())
コード例 #23
0
def test_user_info_init():
    available_slots = 42
    subscription_expiry = 1234

    # UserInfo objects can be created without appointments
    user_info = UserInfo(available_slots, subscription_expiry)
    assert user_info.available_slots == available_slots
    assert user_info.subscription_expiry == subscription_expiry
    assert user_info.appointments == {}

    # But also with, in case they are being restored from the database (the actual data does not matter here)
    appointments = {get_random_value_hex(32): 1 for _ in range(10)}
    user_info = UserInfo(available_slots, subscription_expiry, appointments)
    assert user_info.available_slots == available_slots
    assert user_info.subscription_expiry == subscription_expiry
    assert user_info.appointments == appointments
コード例 #24
0
def test_update_outdated_users_cache(gatekeeper, monkeypatch):
    # update_outdated_users_cache is used to add new entries to the cache and prune old ones if it grows beyond the
    # limit. In normal conditions (no reorg) the method is called once per block height, meaning that the data won't be
    # in the cache when called and it will be afterwards
    # Mock the required BlockProcessor calls from the Gatekeeper
    init_height = 0
    monkeypatch.setattr(gatekeeper.block_processor, "get_block_count",
                        lambda: init_height)

    appointments = {get_random_value_hex(32): 1 for _ in range(10)}
    user_info = UserInfo(available_slots=1,
                         subscription_expiry=init_height + 42,
                         appointments=appointments)
    user_id = get_random_value_hex(32)
    monkeypatch.setitem(gatekeeper.registered_users, user_id, user_info)

    # Check that the entry is not in the cache
    target_height = init_height + gatekeeper.expiry_delta + 42
    assert target_height not in gatekeeper.outdated_users_cache

    # Update the cache and check
    gatekeeper.update_outdated_users_cache(target_height)
    cache_entry = gatekeeper.outdated_users_cache.get(target_height)
    # Values is not meant to be used straightaway for checking, but we can flatten it to check it matches
    flattened_appointments = list(flatten(cache_entry.values()))
    assert list(cache_entry.keys()) == [
        user_id
    ] and flattened_appointments == list(appointments.keys())
コード例 #25
0
def test_get_subscription_info(watcher, generate_dummy_appointment,
                               generate_dummy_tracker, monkeypatch):
    # Tests how get_subscription_info should return no data for empty subscriptions, and the info matching the
    # subscriptions otherwise.

    # Mock a registered user
    available_slots = MAX_APPOINTMENTS
    subscription_expiry = 100
    user_info = UserInfo(available_slots, subscription_expiry)
    monkeypatch.setattr(watcher.gatekeeper, "authenticate_user",
                        lambda x, y: user_id)
    monkeypatch.setattr(watcher.gatekeeper, "has_subscription_expired",
                        lambda x: (False, subscription_expiry))
    monkeypatch.setattr(watcher.gatekeeper, "get_user_info",
                        lambda x: user_info)

    message = "get subscription info"
    signature = Cryptographer.sign(message.encode("utf-8"), user_sk)

    # Empty subscription
    sub_info, locators = watcher.get_subscription_info(signature)
    assert len(locators) == 0
    assert sub_info.available_slots == available_slots
    assert sub_info.subscription_expiry == subscription_expiry

    # Generate some subscription data
    uuid = uuid4().hex
    uuid2 = uuid4().hex
    appointment = generate_dummy_appointment()
    tracker = generate_dummy_tracker()

    # Mock the Watcher, Responder and Gatekeeper's data structures
    monkeypatch.setattr(watcher, "appointments",
                        {uuid: appointment.get_summary()})
    monkeypatch.setattr(watcher.responder, "trackers",
                        {uuid2: tracker.get_summary()})
    user_info.appointments = {uuid: 1, uuid2: 1}

    # And the interaction with the Responder
    monkeypatch.setattr(watcher.responder, "has_tracker", lambda x: True)
    monkeypatch.setattr(watcher.responder, "get_tracker",
                        lambda x: tracker.get_summary())

    sub_info, locators = watcher.get_subscription_info(signature)
    assert set(locators) == {appointment.locator, tracker.locator}
    assert sub_info.available_slots == available_slots
    assert sub_info.subscription_expiry == subscription_expiry
コード例 #26
0
def test_add_appointment(watcher, generate_dummy_appointment, monkeypatch):
    # A registered user with no subscription issues should be able to add an appointment
    appointment = generate_dummy_appointment()

    # Mock a registered user
    expiry = 100
    user_info = UserInfo(MAX_APPOINTMENTS, expiry)
    monkeypatch.setattr(watcher.gatekeeper, "authenticate_user",
                        lambda x, y: user_id)
    monkeypatch.setattr(watcher.gatekeeper, "has_subscription_expired",
                        lambda x: (False, expiry))
    monkeypatch.setattr(watcher.responder, "has_tracker", lambda x: False)
    monkeypatch.setattr(watcher.gatekeeper, "add_update_appointment",
                        lambda x, y, z: MAX_APPOINTMENTS - 1)
    monkeypatch.setattr(watcher.gatekeeper, "get_user_info",
                        lambda x: user_info)

    response = watcher.add_appointment(appointment, appointment.user_signature)
    assert response.get("locator") == appointment.locator
    assert Cryptographer.get_compressed_pk(
        watcher.signing_key.public_key) == Cryptographer.get_compressed_pk(
            Cryptographer.recover_pk(
                receipts.create_appointment_receipt(
                    appointment.user_signature, response.get("start_block")),
                response.get("signature"),
            ))

    # Check that we can also add an already added appointment (same locator)
    response = watcher.add_appointment(appointment, appointment.user_signature)
    assert response.get("locator") == appointment.locator
    assert Cryptographer.get_compressed_pk(
        watcher.signing_key.public_key) == Cryptographer.get_compressed_pk(
            Cryptographer.recover_pk(
                receipts.create_appointment_receipt(
                    appointment.user_signature, response.get("start_block")),
                response.get("signature"),
            ))
    # One one copy is kept since the appointments were the same
    # (the slot count should have not been reduced, but that's something to be tested in the Gatekeeper)
    assert len(watcher.locator_uuid_map[appointment.locator]) == 1

    # If two appointments with the same locator come from different users, they are kept.
    another_user_sk, another_user_pk = generate_keypair()
    another_user_id = Cryptographer.get_compressed_pk(another_user_pk)
    monkeypatch.setattr(watcher.gatekeeper, "authenticate_user",
                        lambda x, y: another_user_id)

    appointment_signature = Cryptographer.sign(appointment.serialize(),
                                               another_user_sk)
    response = watcher.add_appointment(appointment, appointment_signature)
    assert response.get("locator") == appointment.locator
    assert Cryptographer.get_compressed_pk(
        watcher.signing_key.public_key) == Cryptographer.get_compressed_pk(
            Cryptographer.recover_pk(
                receipts.create_appointment_receipt(
                    appointment_signature, response.get("start_block")),
                response.get("signature"),
            ))
    assert len(watcher.locator_uuid_map[appointment.locator]) == 2
コード例 #27
0
def test_get_outdated_users(gatekeeper):
    # Gets a list of users whose subscription gets outdated at a given height
    current_height = gatekeeper.block_processor.get_block_count()
    uuids = [get_random_value_hex(32) for _ in range(20)]
    outdated_users_next = {
        get_random_value_hex(32): UserInfo(
            available_slots=10,
            subscription_expiry=current_height - gatekeeper.expiry_delta + 1,
            appointments={uuids[i]: 1},  # uuid:1 slot
        )
        for i in range(10)
    }

    outdated_users_next_next = {
        get_random_value_hex(32): UserInfo(
            available_slots=10,
            subscription_expiry=current_height - gatekeeper.expiry_delta + 2,
            appointments={uuids[i + 10]: 1},  # uuid:1 slot
        )
        for i in range(10)
    }

    # Add users to the Gatekeeper
    gatekeeper.registered_users.update(outdated_users_next)
    gatekeeper.registered_users.update(outdated_users_next_next)

    # Check that outdated_users_cache are outdated at the current height
    outdated_users = gatekeeper.get_outdated_users(current_height + 1).keys()
    outdated_appointment_uuids = [
        uuid
        for user_appointments in gatekeeper.get_outdated_users(current_height +
                                                               1).values()
        for uuid in user_appointments
    ]
    assert outdated_users == outdated_users_next.keys(
    ) and outdated_appointment_uuids == uuids[:10]

    outdated_users = gatekeeper.get_outdated_users(current_height + 2).keys()
    outdated_appointment_uuids = [
        uuid
        for user_appointments in gatekeeper.get_outdated_users(current_height +
                                                               2).values()
        for uuid in user_appointments
    ]
    assert outdated_users == outdated_users_next_next.keys(
    ) and outdated_appointment_uuids == uuids[10:]
コード例 #28
0
def test_delete_user(user_db_manager):
    # Tests that deleting existing users should work
    stored_users = {}

    # Add some users first
    for _ in range(10):
        user_id = "02" + get_random_value_hex(32)
        user_info = UserInfo(available_slots=42, subscription_expiry=100)
        user_db_manager.store_user(user_id, user_info.to_dict())
        stored_users[user_id] = user_info

    # Deleting existing users should work
    for user_id, user_data in stored_users.items():
        assert user_db_manager.delete_user(user_id) is True

    # There should be no users anymore
    assert not user_db_manager.load_all_users()
コード例 #29
0
def test_load_all_users(user_db_manager):
    # Tests loading all the users in the database
    stored_users = {}

    # There should be no users at the moment
    assert user_db_manager.load_all_users() == {}
    stored_users = {}

    # Adding some and checking we get them all
    for i in range(10):
        user_id = "02" + get_random_value_hex(32)
        user_info = UserInfo(available_slots=42, subscription_expiry=100)
        user_db_manager.store_user(user_id, user_info.to_dict())
        stored_users[user_id] = user_info.to_dict()

    all_users = user_db_manager.load_all_users()
    assert all_users == stored_users
コード例 #30
0
def test_load_all_users(user_db_manager):
    # There should be no users at the moment
    assert user_db_manager.load_all_users() == {}
    stored_users = {}

    # Adding some and checking we get them all
    for i in range(10):
        user_id = "02" + get_random_value_hex(32)
        user_info = UserInfo(available_slots=42, subscription_expiry=100)
        user_db_manager.store_user(user_id, user_info.to_dict())
        stored_users[user_id] = user_info.to_dict()

    all_users = user_db_manager.load_all_users()

    assert set(all_users.keys()) == set(stored_users.keys())
    for k, v in all_users.items():
        assert stored_users[k] == v