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)
Exemple #2
0
def test_build_appointments():
    appointments_data = {}

    # Create some appointment data
    for i in range(10):
        appointment, _ = generate_dummy_appointment(real_height=False)
        uuid = uuid4().hex

        appointments_data[uuid] = appointment.to_dict()

        # Add some additional appointments that share the same locator to test all the builder's cases
        if i % 2 == 0:
            locator = appointment.locator
            appointment, _ = generate_dummy_appointment(real_height=False)
            uuid = uuid4().hex
            appointment.locator = locator

            appointments_data[uuid] = appointment.to_dict()

    # Use the builder to create the data structures
    appointments, locator_uuid_map = Builder.build_appointments(
        appointments_data)

    # Check that the created appointments match the data
    for uuid, appointment in appointments.items():
        assert uuid in appointments_data.keys()
        assert appointments_data[uuid].get("locator") == appointment.get(
            "locator")
        assert appointments_data[uuid].get("end_time") == appointment.get(
            "end_time")
        assert uuid in locator_uuid_map[appointment.get("locator")]
Exemple #3
0
def test_add_update_appointment(gatekeeper):
    # add_update_appointment should decrease the slot count if a new appointment is added
    # let's add a new user
    sk, pk = generate_keypair()
    user_id = Cryptographer.get_compressed_pk(pk)
    gatekeeper.add_update_user(user_id)

    # And now update add a new appointment
    appointment, _ = generate_dummy_appointment()
    appointment_uuid = get_random_value_hex(16)
    remaining_slots = gatekeeper.add_update_appointment(
        user_id, appointment_uuid, appointment)

    # This is a standard size appointment, so it should have reduced the slots by one
    assert appointment_uuid in gatekeeper.registered_users[
        user_id].appointments
    assert remaining_slots == config.get("SUBSCRIPTION_SLOTS") - 1

    # Updates can leave the count as is, decrease it, or increase it, depending on the appointment size (modulo
    # ENCRYPTED_BLOB_MAX_SIZE_HEX)

    # Appointments of the same size leave it as is
    appointment_same_size, _ = generate_dummy_appointment()
    remaining_slots = gatekeeper.add_update_appointment(
        user_id, appointment_uuid, appointment)
    assert appointment_uuid in gatekeeper.registered_users[
        user_id].appointments
    assert remaining_slots == config.get("SUBSCRIPTION_SLOTS") - 1

    # Bigger appointments decrease it
    appointment_x2_size = appointment_same_size
    appointment_x2_size.encrypted_blob = "A" * (ENCRYPTED_BLOB_MAX_SIZE_HEX +
                                                1)
    remaining_slots = gatekeeper.add_update_appointment(
        user_id, appointment_uuid, appointment_x2_size)
    assert appointment_uuid in gatekeeper.registered_users[
        user_id].appointments
    assert remaining_slots == config.get("SUBSCRIPTION_SLOTS") - 2

    # Smaller appointments increase it
    remaining_slots = gatekeeper.add_update_appointment(
        user_id, appointment_uuid, appointment)
    assert remaining_slots == config.get("SUBSCRIPTION_SLOTS") - 1

    # If the appointment needs more slots than there's free, it should fail
    gatekeeper.registered_users[user_id].available_slots = 1
    appointment_uuid = get_random_value_hex(16)
    with pytest.raises(NotEnoughSlots):
        gatekeeper.add_update_appointment(user_id, appointment_uuid,
                                          appointment_x2_size)
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))
def test_filter_valid_breaches(watcher):
    dispute_txid = "0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9"
    encrypted_blob = (
        "a62aa9bb3c8591e4d5de10f1bd49db92432ce2341af55762cdc9242c08662f97f5f47da0a1aa88373508cd6e67e87eefddeca0cee98c1"
        "967ec1c1ecbb4c5e8bf08aa26159214e6c0bc4b2c7c247f87e7601d15c746fc4e711be95ba0e363001280138ba9a65b06c4aa6f592b21"
        "3635ee763984d522a4c225814510c8f7ab0801f36d4a68f5ee7dd3930710005074121a172c29beba79ed647ebaf7e7fab1bbd9a208251"
        "ef5486feadf2c46e33a7d66adf9dbbc5f67b55a34b1b3c4909dd34a482d759b0bc25ecd2400f656db509466d7479b5b92a2fadabccc9e"
        "c8918da8979a9feadea27531643210368fee494d3aaa4983e05d6cf082a49105e2f8a7c7821899239ba7dee12940acd7d8a629894b5d31"
        "e94b439cfe8d2e9f21e974ae5342a70c91e8"
    )

    dummy_appointment, _ = generate_dummy_appointment()
    dummy_appointment.encrypted_blob = encrypted_blob
    dummy_appointment.locator = compute_locator(dispute_txid)
    uuid = uuid4().hex

    appointments = {uuid: dummy_appointment}
    locator_uuid_map = {dummy_appointment.locator: [uuid]}
    breaches = {dummy_appointment.locator: dispute_txid}

    for uuid, appointment in appointments.items():
        watcher.appointments[uuid] = {"locator": appointment.locator, "user_id": appointment.user_id}
        watcher.db_manager.store_watcher_appointment(uuid, dummy_appointment.to_dict())
        watcher.db_manager.create_append_locator_map(dummy_appointment.locator, uuid)

    watcher.locator_uuid_map = locator_uuid_map

    valid_breaches, invalid_breaches = watcher.filter_breaches(breaches)

    # We have "triggered" a single breach and it was valid.
    assert len(invalid_breaches) == 0 and len(valid_breaches) == 1
def test_filter_breaches_random_data(watcher):
    appointments = {}
    locator_uuid_map = {}
    breaches = {}

    for i in range(TEST_SET_SIZE):
        dummy_appointment, _ = generate_dummy_appointment()
        uuid = uuid4().hex
        appointments[uuid] = {"locator": dummy_appointment.locator, "user_id": dummy_appointment.user_id}
        watcher.db_manager.store_watcher_appointment(uuid, dummy_appointment.to_dict())
        watcher.db_manager.create_append_locator_map(dummy_appointment.locator, uuid)

        locator_uuid_map[dummy_appointment.locator] = [uuid]

        if i % 2:
            dispute_txid = get_random_value_hex(32)
            breaches[dummy_appointment.locator] = dispute_txid

    watcher.locator_uuid_map = locator_uuid_map
    watcher.appointments = appointments

    valid_breaches, invalid_breaches = watcher.filter_breaches(breaches)

    # We have "triggered" TEST_SET_SIZE/2 breaches, all of them invalid.
    assert len(valid_breaches) == 0 and len(invalid_breaches) == TEST_SET_SIZE / 2
def test_check_breach(watcher):
    # A breach will be flagged as valid only if the encrypted blob can be properly decrypted and the resulting data
    # matches a transaction format.
    uuid = uuid4().hex
    appointment, dispute_tx = generate_dummy_appointment()
    dispute_txid = watcher.block_processor.decode_raw_transaction(dispute_tx).get("txid")

    penalty_txid, penalty_rawtx = watcher.check_breach(uuid, appointment, dispute_txid)
    assert Cryptographer.encrypt(penalty_rawtx, dispute_txid) == appointment.encrypted_blob
def test_add_appointment_non_registered(watcher):
    # Appointments from non-registered users should fail
    user_sk, user_pk = generate_keypair()

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

    with pytest.raises(AuthenticationFailure, match="User not found"):
        watcher.add_appointment(appointment, appointment_signature)
def test_add_appointment_no_slots(watcher):
    # Appointments from register users with no available slots should aso fail
    user_sk, user_pk = generate_keypair()
    user_id = Cryptographer.get_compressed_pk(user_pk)
    watcher.gatekeeper.registered_users[user_id] = UserInfo(available_slots=0, subscription_expiry=10)

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

    with pytest.raises(NotEnoughSlots):
        watcher.add_appointment(appointment, appointment_signature)
def test_check_breach_random_data(watcher):
    # If a breach triggers an appointment with random data as encrypted blob, the check should fail.
    uuid = uuid4().hex
    appointment, dispute_tx = generate_dummy_appointment()
    dispute_txid = watcher.block_processor.decode_raw_transaction(dispute_tx).get("txid")

    # Set the blob to something "random"
    appointment.encrypted_blob = get_random_value_hex(200)

    with pytest.raises(EncryptionError):
        watcher.check_breach(uuid, appointment, dispute_txid)
def test_check_breach_invalid_transaction(watcher):
    # If the breach triggers an appointment with data that can be decrypted but does not match a transaction, it should
    # fail
    uuid = uuid4().hex
    appointment, dispute_tx = generate_dummy_appointment()
    dispute_txid = watcher.block_processor.decode_raw_transaction(dispute_tx).get("txid")

    # Set the blob to something "random"
    appointment.encrypted_blob = Cryptographer.encrypt(get_random_value_hex(200), dispute_txid)

    with pytest.raises(InvalidTransactionFormat):
        watcher.check_breach(uuid, appointment, dispute_txid)
def test_add_too_many_appointments(watcher):
    # Any appointment on top of those should fail
    watcher.appointments = dict()

    for _ in range(config.get("MAX_APPOINTMENTS")):
        appointment, dispute_tx = generate_dummy_appointment(
            start_time_offset=START_TIME_OFFSET,
            end_time_offset=END_TIME_OFFSET)
        added_appointment, sig = watcher.add_appointment(appointment)

        assert added_appointment is True
        assert Cryptographer.verify_rpk(
            watcher.signing_key.public_key,
            Cryptographer.recover_pk(appointment.serialize(), sig))

    appointment, dispute_tx = generate_dummy_appointment(
        start_time_offset=START_TIME_OFFSET, end_time_offset=END_TIME_OFFSET)
    added_appointment, sig = watcher.add_appointment(appointment)

    assert added_appointment is False
    assert sig is None
def create_appointments(n):
    locator_uuid_map = dict()
    appointments = dict()
    dispute_txs = []

    for i in range(n):
        appointment, dispute_tx = generate_dummy_appointment()
        uuid = uuid4().hex

        appointments[uuid] = appointment
        locator_uuid_map[appointment.locator] = [uuid]
        dispute_txs.append(dispute_tx)

    return appointments, locator_uuid_map, dispute_txs
Exemple #14
0
def test_get_all_appointments_watcher(api, client, get_all_db_manager):
    # Let's reset the dbs so we can test this clean
    api.watcher.db_manager = get_all_db_manager
    api.watcher.responder.db_manager = get_all_db_manager

    # Check that they are wiped clean
    r = client.get(get_all_appointment_endpoint)
    assert r.status_code == HTTP_OK
    assert len(r.json.get("watcher_appointments")) == 0 and len(r.json.get("responder_trackers")) == 0

    # Add some appointments to the Watcher db
    non_triggered_appointments = {}
    for _ in range(10):
        uuid = get_random_value_hex(16)
        appointment, _ = generate_dummy_appointment()
        appointment.locator = get_random_value_hex(16)
        non_triggered_appointments[uuid] = appointment.to_dict()
        api.watcher.db_manager.store_watcher_appointment(uuid, appointment.to_dict())

    triggered_appointments = {}
    for _ in range(10):
        uuid = get_random_value_hex(16)
        appointment, _ = generate_dummy_appointment()
        appointment.locator = get_random_value_hex(16)
        triggered_appointments[uuid] = appointment.to_dict()
        api.watcher.db_manager.store_watcher_appointment(uuid, appointment.to_dict())
        api.watcher.db_manager.create_triggered_appointment_flag(uuid)

    # We should only get the non-triggered appointments
    r = client.get(get_all_appointment_endpoint)
    assert r.status_code == HTTP_OK

    watcher_locators = [v["locator"] for k, v in r.json["watcher_appointments"].items()]
    local_locators = [appointment["locator"] for uuid, appointment in non_triggered_appointments.items()]

    assert set(watcher_locators) == set(local_locators)
    assert len(r.json["responder_trackers"]) == 0
def create_appointments(n):
    locator_uuid_map = dict()
    appointments = dict()
    dispute_txs = []

    for i in range(n):
        appointment, dispute_tx = generate_dummy_appointment(
            start_time_offset=START_TIME_OFFSET,
            end_time_offset=END_TIME_OFFSET)
        uuid = uuid4().hex

        appointments[uuid] = appointment
        locator_uuid_map[appointment.locator] = [uuid]
        dispute_txs.append(dispute_tx)

    return appointments, locator_uuid_map, dispute_txs
Exemple #16
0
def test_store_load_triggered_appointment(db_manager):
    db_watcher_appointments = db_manager.load_watcher_appointments()
    db_watcher_appointments_with_triggered = db_manager.load_watcher_appointments(
        include_triggered=True)

    assert db_watcher_appointments == db_watcher_appointments_with_triggered

    # Create an appointment flagged as triggered
    triggered_appointment, _ = generate_dummy_appointment(real_height=False)
    uuid = uuid4().hex
    db_manager.store_watcher_appointment(uuid, triggered_appointment.to_json())
    db_manager.create_triggered_appointment_flag(uuid)

    # The new appointment is grabbed only if we set include_triggered
    assert db_watcher_appointments == db_manager.load_watcher_appointments()
    assert uuid in db_manager.load_watcher_appointments(include_triggered=True)
Exemple #17
0
def test_add_appointment_in_cache_cannot_decrypt(api, client):
    api.watcher.gatekeeper.registered_users[user_id] = UserInfo(available_slots=1, subscription_expiry=0)
    appointment, dispute_tx = generate_dummy_appointment()
    appointment.encrypted_blob = appointment.encrypted_blob[::-1]
    appointment_signature = Cryptographer.sign(appointment.serialize(), user_sk)

    # Add the data to the cache
    dispute_txid = api.watcher.block_processor.decode_raw_transaction(dispute_tx).get("txid")
    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
    )
Exemple #18
0
def test_add_too_many_appointment(api, client):
    # Give slots to the user
    api.watcher.gatekeeper.registered_users[user_id] = UserInfo(available_slots=200, subscription_expiry=0)

    free_appointment_slots = MAX_APPOINTMENTS - len(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") == api.watcher.last_known_block
        else:
            assert r.status_code == HTTP_SERVICE_UNAVAILABLE
def test_add_appointment(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)

    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 - 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(appointment.serialize(), 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=10
    )

    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(appointment.serialize(), response.get("signature"))
    )
    assert response.get("available_slots") == available_slots - 1
    assert len(watcher.locator_uuid_map[appointment.locator]) == 2
def test_add_appointment(watcher):
    # We should be able to add appointments up to the limit
    for _ in range(10):
        appointment, dispute_tx = generate_dummy_appointment(
            start_time_offset=START_TIME_OFFSET,
            end_time_offset=END_TIME_OFFSET)
        added_appointment, sig = watcher.add_appointment(appointment)

        assert added_appointment is True
        assert Cryptographer.verify_rpk(
            watcher.signing_key.public_key,
            Cryptographer.recover_pk(appointment.serialize(), sig))

        # Check that we can also add an already added appointment (same locator)
        added_appointment, sig = watcher.add_appointment(appointment)

        assert added_appointment is True
        assert Cryptographer.verify_rpk(
            watcher.signing_key.public_key,
            Cryptographer.recover_pk(appointment.serialize(), sig))
Exemple #21
0
def test_add_appointment_in_cache(api, client):
    api.watcher.gatekeeper.registered_users[user_id] = UserInfo(available_slots=1, subscription_expiry=0)
    appointment, dispute_tx = generate_dummy_appointment()
    appointment_signature = Cryptographer.sign(appointment.serialize(), user_sk)

    # Add the data to the cache
    dispute_txid = api.watcher.block_processor.decode_raw_transaction(dispute_tx).get("txid")
    api.watcher.locator_cache.cache[appointment.locator] = dispute_txid

    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
    )

    # Trying to add it again should fail, since it is already in the Responder
    r = add_appointment(client, {"appointment": appointment.to_dict(), "signature": appointment_signature}, user_id)
    assert r.status_code == HTTP_BAD_REQUEST and r.json.get("error_code") == errors.APPOINTMENT_ALREADY_TRIGGERED

    # The appointment would be rejected even if the data is not in the cache provided it has been triggered
    del api.watcher.locator_cache.cache[appointment.locator]
    r = add_appointment(client, {"appointment": appointment.to_dict(), "signature": appointment_signature}, user_id)
    assert r.status_code == HTTP_BAD_REQUEST and r.json.get("error_code") == errors.APPOINTMENT_ALREADY_TRIGGERED
def test_add_appointment_in_cache_invalid_transaction(watcher):
    # 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=10)

    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
    response = watcher.add_appointment(appointment, Cryptographer.sign(appointment.serialize(), user_sk))

    # 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.serialize(), 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()]
Exemple #23
0
def appointment():
    appointment, dispute_tx = generate_dummy_appointment()
    locator_dispute_tx_map[appointment.locator] = dispute_tx

    return appointment
Exemple #24
0
def watcher_appointments():
    return {
        uuid4().hex: generate_dummy_appointment(real_height=False)[0]
        for _ in range(10)
    }
def watcher_appointments():
    return {uuid4().hex: generate_dummy_appointment()[0] for _ in range(10)}