예제 #1
0
def test_add_appointment():
    # Simulate a request to add_appointment for dummy_appointment, make sure that the right endpoint is requested
    # and the return value is True
    appointment = teos_client.create_appointment(dummy_appointment_data)
    user_signature = Cryptographer.sign(appointment.serialize(), dummy_user_sk)
    appointment_receipt = receipts.create_appointment_receipt(
        user_signature, CURRENT_HEIGHT)

    response = {
        "locator": dummy_appointment.locator,
        "signature": Cryptographer.sign(appointment_receipt, dummy_teos_sk),
        "available_slots": 100,
        "start_block": CURRENT_HEIGHT,
        "subscription_expiry": CURRENT_HEIGHT + 4320,
    }
    responses.add(responses.POST,
                  add_appointment_endpoint,
                  json=response,
                  status=200)

    result = teos_client.add_appointment(
        Appointment.from_dict(dummy_appointment_data), dummy_user_sk,
        dummy_teos_id, teos_url)

    assert len(responses.calls) == 1
    assert responses.calls[0].request.url == add_appointment_endpoint
    assert result
def test_get_appointment_non_registered(internal_api, stub,
                                        generate_dummy_appointment):
    # If the user is not registered or the appointment does not belong to him the response should be NOT_FOUND
    stub.register(RegisterRequest(user_id=user_id))
    another_user_sk, another_user_pk = generate_keypair()

    # Send the appointment first
    appointment, _ = generate_dummy_appointment()
    appointment_signature = Cryptographer.sign(appointment.serialize(),
                                               user_sk)
    send_appointment(stub, appointment, appointment_signature)

    # Request it back
    message = f"get appointment {appointment.locator}"
    request_signature = Cryptographer.sign(message.encode("utf-8"),
                                           another_user_sk)

    with pytest.raises(grpc.RpcError) as e:
        stub.get_appointment(
            GetAppointmentRequest(locator=appointment.locator,
                                  signature=request_signature))

    assert e.value.code() == grpc.StatusCode.NOT_FOUND
    assert "Appointment not found" in e.value.details()

    # Notice how the request will succeed if `user` (user_id) requests it
    request_signature = Cryptographer.sign(message.encode("utf-8"), user_sk)
    response = stub.get_appointment(
        GetAppointmentRequest(locator=appointment.locator,
                              signature=request_signature))
    assert isinstance(response, GetAppointmentResponse)
예제 #3
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))
def test_get_appointment_subscription_expired(internal_api, stub,
                                              generate_dummy_appointment):
    # UNAUTHENTICATED is returned if the subscription has expired
    stub.register(RegisterRequest(user_id=user_id))

    # Send the appointment first
    appointment, _ = generate_dummy_appointment()
    appointment_signature = Cryptographer.sign(appointment.serialize(),
                                               user_sk)
    send_appointment(stub, appointment, appointment_signature)

    # Modify the user data so the subscription has already ended
    expiry = internal_api.watcher.block_processor.get_block_count(
    ) - internal_api.watcher.gatekeeper.expiry_delta - 1
    internal_api.watcher.gatekeeper.registered_users[
        user_id].subscription_expiry = expiry

    # Request it back
    message = f"get appointment {appointment.locator}"
    request_signature = Cryptographer.sign(message.encode("utf-8"), user_sk)

    with pytest.raises(grpc.RpcError) as e:
        stub.get_appointment(
            GetAppointmentRequest(locator=appointment.locator,
                                  signature=request_signature))

    assert e.value.code() == grpc.StatusCode.UNAUTHENTICATED
    assert "Your subscription expired at" in e.value.details()
예제 #5
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)
예제 #6
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())
예제 #7
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())
def test_sign():
    # Otherwise we should get a signature
    sk, _ = generate_keypair()
    message = b""

    assert Cryptographer.sign(message, sk) is not None
    assert isinstance(Cryptographer.sign(message, sk), str)
예제 #9
0
def test_add_appointment_with_invalid_signature():
    # Simulate a request to add_appointment for dummy_appointment, but sign with a different key,
    # make sure that the right endpoint is requested, but the return value is False
    appointment = teos_client.create_appointment(dummy_appointment_data)
    user_signature = Cryptographer.sign(appointment.serialize(), dummy_user_sk)
    appointment_receipt = receipts.create_appointment_receipt(
        user_signature, CURRENT_HEIGHT)

    # Sign with a bad key
    response = {
        "locator": dummy_appointment.locator,
        "signature": Cryptographer.sign(appointment_receipt, another_sk),
        "available_slots": 100,
        "start_block": CURRENT_HEIGHT,
        "subscription_expiry": CURRENT_HEIGHT + 4320,
    }

    responses.add(responses.POST,
                  add_appointment_endpoint,
                  json=response,
                  status=200)

    with pytest.raises(TowerResponseError):
        teos_client.add_appointment(
            Appointment.from_dict(dummy_appointment_data), dummy_user_sk,
            dummy_teos_id, teos_url)

    # should have performed exactly 1 network request
    assert len(responses.calls) == 1
예제 #10
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
예제 #11
0
def test_add_appointment_trigger_on_cache_cannot_decrypt(bitcoin_cli):
    commitment_tx, penalty_tx = create_txs(bitcoin_cli)

    # Let's send the commitment to the network and mine a block
    broadcast_transaction_and_mine_block(bitcoin_cli, commitment_tx, bitcoin_cli.getnewaddress())
    sleep(1)

    # The appointment data is built using a random 32-byte value.
    appointment_data = build_appointment_data(get_random_value_hex(32), penalty_tx)

    # We cannot use teos_cli.add_appointment here since it computes the locator internally, so let's do it manually.
    appointment_data["locator"] = compute_locator(bitcoin_cli.decoderawtransaction(commitment_tx).get("txid"))
    appointment_data["encrypted_blob"] = Cryptographer.encrypt(penalty_tx, get_random_value_hex(32))
    appointment = Appointment.from_dict(appointment_data)

    signature = Cryptographer.sign(appointment.serialize(), user_sk)
    data = {"appointment": appointment.to_dict(), "signature": signature}

    # Send appointment to the server.
    response = teos_cli.post_request(data, teos_add_appointment_endpoint)
    response_json = teos_cli.process_post_response(response)

    # Check that the server has accepted the appointment
    signature = response_json.get("signature")
    rpk = Cryptographer.recover_pk(appointment.serialize(), signature)
    assert teos_id == Cryptographer.get_compressed_pk(rpk)
    assert response_json.get("locator") == appointment.locator

    # The appointment should should have been inmediately dropped
    with pytest.raises(TowerResponseError):
        get_appointment_info(appointment_data["locator"])
예제 #12
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
예제 #13
0
def test_appointment_wrong_decryption_key(bitcoin_cli):
    # This tests an appointment encrypted with a key that has not been derived from the same source as the locator.
    # Therefore the tower won't be able to decrypt the blob once the appointment is triggered.
    commitment_tx, penalty_tx = create_txs(bitcoin_cli)

    # The appointment data is built using a random 32-byte value.
    appointment_data = build_appointment_data(get_random_value_hex(32), penalty_tx)

    # We cannot use teos_cli.add_appointment here since it computes the locator internally, so let's do it manually.
    # We will encrypt the blob using the random value and derive the locator from the commitment tx.
    appointment_data["locator"] = compute_locator(bitcoin_cli.decoderawtransaction(commitment_tx).get("txid"))
    appointment_data["encrypted_blob"] = Cryptographer.encrypt(penalty_tx, get_random_value_hex(32))
    appointment = Appointment.from_dict(appointment_data)

    signature = Cryptographer.sign(appointment.serialize(), user_sk)
    data = {"appointment": appointment.to_dict(), "signature": signature}

    # Send appointment to the server.
    response = teos_cli.post_request(data, teos_add_appointment_endpoint)
    response_json = teos_cli.process_post_response(response)

    # Check that the server has accepted the appointment
    signature = response_json.get("signature")
    rpk = Cryptographer.recover_pk(appointment.serialize(), signature)
    assert teos_id == Cryptographer.get_compressed_pk(rpk)
    assert response_json.get("locator") == appointment.locator

    # Trigger the appointment
    new_addr = bitcoin_cli.getnewaddress()
    broadcast_transaction_and_mine_block(bitcoin_cli, commitment_tx, new_addr)

    # The appointment should have been removed since the decryption failed.
    with pytest.raises(TowerResponseError):
        get_appointment_info(appointment.locator)
예제 #14
0
def test_inspect(run_bitcoind):
    # At this point every single check function has been already tested, let's test inspect with an invalid and a valid
    # appointments.

    client_sk, client_pk = generate_keypair()
    client_pk_hex = client_pk.format().hex()

    # Valid appointment
    locator = get_random_value_hex(LOCATOR_LEN_BYTES)
    start_time = block_processor.get_block_count() + 5
    end_time = start_time + 20
    to_self_delay = MIN_TO_SELF_DELAY
    encrypted_blob = get_random_value_hex(64)

    appointment_data = {
        "locator": locator,
        "start_time": start_time,
        "end_time": end_time,
        "to_self_delay": to_self_delay,
        "encrypted_blob": encrypted_blob,
    }

    signature = Cryptographer.sign(
        Appointment.from_dict(appointment_data).serialize(), client_sk)

    appointment = inspector.inspect(appointment_data, signature, client_pk_hex)

    assert (type(appointment) == Appointment and appointment.locator == locator
            and appointment.start_time == start_time
            and appointment.end_time == end_time
            and appointment.to_self_delay == to_self_delay
            and appointment.encrypted_blob.data == encrypted_blob)
예제 #15
0
def test_get_appointment_in_watcher(internal_api, client, appointment,
                                    monkeypatch):
    # Mock the appointment in the Watcher
    uuid = hash_160("{}{}".format(appointment.locator, user_id))
    extended_appointment_summary = {
        "locator": appointment.locator,
        "user_id": user_id
    }
    internal_api.watcher.appointments[uuid] = extended_appointment_summary
    internal_api.watcher.db_manager.store_watcher_appointment(
        uuid, appointment.to_dict())

    # mock the gatekeeper (user won't be registered if the previous tests weren't ran)
    monkeypatch.setattr(internal_api.watcher.gatekeeper, "authenticate_user",
                        mock_authenticate_user)

    # Next we can request it
    message = "get appointment {}".format(appointment.locator)
    signature = Cryptographer.sign(message.encode("utf-8"), user_sk)
    data = {"locator": appointment.locator, "signature": signature}
    r = client.post(get_appointment_endpoint, json=data)
    assert r.status_code == HTTP_OK

    # Check that the appointment is on the Watcher
    assert r.json.get("status") == AppointmentStatus.BEING_WATCHED

    # Cast the extended appointment (used by the tower) to a regular appointment (used by the user)
    appointment = Appointment.from_dict(appointment.to_dict())

    # Check the the sent appointment matches the received one
    assert r.json.get("locator") == appointment.locator
    assert appointment.to_dict() == r.json.get("appointment")
예제 #16
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
    )
예제 #17
0
def test_get_appointment_in_responder(api, client, appointment):
    # Mock the appointment in the Responder
    tracker_data = {
        "locator": appointment.locator,
        "dispute_txid": get_random_value_hex(32),
        "penalty_txid": get_random_value_hex(32),
        "penalty_rawtx": get_random_value_hex(250),
        "user_id": get_random_value_hex(16),
    }
    tx_tracker = TransactionTracker.from_dict(tracker_data)

    uuid = hash_160("{}{}".format(appointment.locator, user_id))
    api.watcher.db_manager.create_triggered_appointment_flag(uuid)
    api.watcher.responder.db_manager.store_responder_tracker(uuid, tx_tracker.to_dict())

    # Request back the data
    message = "get appointment {}".format(appointment.locator)
    signature = Cryptographer.sign(message.encode("utf-8"), user_sk)
    data = {"locator": appointment.locator, "signature": signature}

    # Next we can request it
    r = client.post(get_appointment_endpoint, json=data)
    assert r.status_code == HTTP_OK

    # Check that the appointment is on the Responder
    assert r.json.get("status") == "dispute_responded"

    # Check the the sent appointment matches the received one
    assert tx_tracker.locator == r.json.get("locator")
    assert tx_tracker.dispute_txid == r.json.get("appointment").get("dispute_txid")
    assert tx_tracker.penalty_txid == r.json.get("appointment").get("penalty_txid")
    assert tx_tracker.penalty_rawtx == r.json.get("appointment").get("penalty_rawtx")
예제 #18
0
def test_get_appointment_in_responder(internal_api, client,
                                      generate_dummy_tracker, monkeypatch):
    tx_tracker = generate_dummy_tracker()

    # Mock the appointment in the Responder
    uuid = hash_160("{}{}".format(tx_tracker.locator, user_id))
    internal_api.watcher.responder.trackers[uuid] = tx_tracker.get_summary()
    internal_api.watcher.responder.db_manager.store_responder_tracker(
        uuid, tx_tracker.to_dict())

    # mock the gatekeeper (user won't be registered if the previous tests weren't ran)
    monkeypatch.setattr(internal_api.watcher.gatekeeper, "authenticate_user",
                        mock_authenticate_user)

    # Request back the data
    message = "get appointment {}".format(tx_tracker.locator)
    signature = Cryptographer.sign(message.encode("utf-8"), user_sk)
    data = {"locator": tx_tracker.locator, "signature": signature}

    # Next we can request it
    r = client.post(get_appointment_endpoint, json=data)
    assert r.status_code == HTTP_OK

    # Check that the appointment is on the Responder
    assert r.json.get("status") == AppointmentStatus.DISPUTE_RESPONDED

    # Check the the sent appointment matches the received one
    assert tx_tracker.locator == r.json.get("locator")
    assert tx_tracker.dispute_txid == r.json.get("appointment").get(
        "dispute_txid")
    assert tx_tracker.penalty_txid == r.json.get("appointment").get(
        "penalty_txid")
    assert tx_tracker.penalty_rawtx == r.json.get("appointment").get(
        "penalty_rawtx")
예제 #19
0
def test_register(api, client, monkeypatch):
    # Tests registering a user within the tower

    # Monkeypatch the response from the InternalAPI so the user is accepted
    slots = config.get("SUBSCRIPTION_SLOTS")
    expiry = config.get("SUBSCRIPTION_DURATION")
    receipt = receipts.create_registration_receipt(user_id, slots, expiry)
    signature = Cryptographer.sign(receipt, teos_sk)
    response = RegisterResponse(
        user_id=user_id,
        available_slots=slots,
        subscription_expiry=expiry,
        subscription_signature=signature,
    )
    monkeypatch.setattr(api.stub, "register", lambda x: response)

    #  Send the register request
    data = {"public_key": user_id}
    r = client.post(register_endpoint, json=data)

    # Check the reply
    assert r.status_code == HTTP_OK
    assert r.json.get("public_key") == user_id
    assert r.json.get("available_slots") == config.get("SUBSCRIPTION_SLOTS")
    assert r.json.get("subscription_expiry") == config.get(
        "SUBSCRIPTION_DURATION")
    rpk = Cryptographer.recover_pk(receipt,
                                   r.json.get("subscription_signature"))
    assert Cryptographer.get_compressed_pk(rpk) == teos_id
예제 #20
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
예제 #21
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)
예제 #22
0
def test_add_appointment_multiple_times_same_user(api, client,
                                                  generate_dummy_appointment,
                                                  monkeypatch):
    # Multiple appointments with the same locator should be valid. At the API level we only need to test that
    # the request goes trough.
    appointment = generate_dummy_appointment()
    appointment_signature = Cryptographer.sign(appointment.serialize(),
                                               user_sk)

    # Monkeypatch the InternalAPI return
    slots = 10
    subscription_expiry = 100
    appointment = generate_dummy_appointment()
    response = {
        "locator": appointment.locator,
        "start_block": appointment.start_block,
        "signature": get_random_value_hex(70),
        "available_slots": slots,
        "subscription_expiry": subscription_expiry,
    }
    monkeypatch.setattr(api.stub, "add_appointment",
                        lambda x: AddAppointmentResponse(**response))

    for _ in range(MULTIPLE_APPOINTMENTS):
        r = client.post(add_appointment_endpoint,
                        json={
                            "appointment": appointment.to_dict(),
                            "signature": appointment_signature
                        })
        assert r.status_code == HTTP_OK
        assert r.json.get("available_slots") == slots
        assert r.json.get("start_block") == appointment.start_block
예제 #23
0
def test_add_appointment_not_registered_no_enough_slots(
        api, client, generate_dummy_appointment, monkeypatch):
    # A properly formatted add appointment request:
    #   - from a non-registered user
    #   - from a user with no free slots
    #   - from a user with no enough free slots
    # should fail. To prevent probing, they all fail as UNAUTHENTICATED. Further testing can be done in the Watcher
    # but it's transparent from the API POV.

    # Mock the non-registered user (gRPC UNAUTHENTICATED error)
    e_code = grpc.StatusCode.UNAUTHENTICATED
    monkeypatch.setattr(api.stub, "add_appointment", raise_grpc_error)
    monkeypatch.setattr(rpc_error, "code", lambda: e_code)
    monkeypatch.setattr(rpc_error, "details", lambda: "")

    # Send the data
    appointment = generate_dummy_appointment()
    appointment_signature = Cryptographer.sign(appointment.serialize(),
                                               user_sk)
    r = client.post(add_appointment_endpoint,
                    json={
                        "appointment": appointment.to_dict(),
                        "signature": appointment_signature
                    })
    assert r.status_code == HTTP_BAD_REQUEST
    assert errors.APPOINTMENT_INVALID_SIGNATURE_OR_SUBSCRIPTION_ERROR == r.json.get(
        "error_code")
예제 #24
0
def test_add_appointment(api, client, generate_dummy_appointment, monkeypatch):
    # Test adding a properly formatted appointment

    # Monkeypatch the InternalAPI return
    slots = 10
    subscription_expiry = 100
    appointment = generate_dummy_appointment()
    response = {
        "locator": appointment.locator,
        "start_block": appointment.start_block,
        "signature": get_random_value_hex(70),
        "available_slots": slots,
        "subscription_expiry": subscription_expiry,
    }
    monkeypatch.setattr(api.stub, "add_appointment",
                        lambda x: AddAppointmentResponse(**response))

    # Properly formatted appointment
    appointment_signature = Cryptographer.sign(appointment.serialize(),
                                               user_sk)
    r = client.post(add_appointment_endpoint,
                    json={
                        "appointment": appointment.to_dict(),
                        "signature": appointment_signature
                    })

    assert r.status_code == HTTP_OK
    assert r.json == response
예제 #25
0
def test_get_appointment_watcher(api, client, generate_dummy_appointment,
                                 monkeypatch):
    # Mock the appointment in the Watcher
    appointment = generate_dummy_appointment()
    app_data = AppointmentData(appointment=AppointmentProto(
        locator=appointment.locator,
        encrypted_blob=appointment.encrypted_blob,
        to_self_delay=appointment.to_self_delay,
    ))
    status = AppointmentStatus.BEING_WATCHED
    monkeypatch.setattr(
        api.stub, "get_appointment",
        lambda x: GetAppointmentResponse(appointment_data=app_data,
                                         status=status))

    # Request it
    message = "get appointment {}".format(appointment.locator)
    signature = Cryptographer.sign(message.encode("utf-8"), user_sk)
    data = {"locator": appointment.locator, "signature": signature}
    r = client.post(get_appointment_endpoint, json=data)
    assert r.status_code == HTTP_OK

    # Check that requested appointment data matches the mocked one
    # Cast the extended appointment (used by the tower) to a regular appointment (used by the user)
    local_appointment = Appointment.from_dict(appointment.to_dict())
    assert r.json.get("status") == AppointmentStatus.BEING_WATCHED
    assert r.json.get("appointment") == local_appointment.to_dict()
예제 #26
0
    def register(self):
        user_id = request.get_json().get("public_key")

        available_slots = 100 if user_id not in self.users else self.users[
            user_id]["available_slots"] + 100
        subscription_expiry = CURRENT_HEIGHT + 4320
        self.users[user_id] = {
            "available_slots": available_slots,
            "subscription_expiry": subscription_expiry
        }
        registration_receipt = receipts.create_registration_receipt(
            user_id, available_slots, subscription_expiry)

        rcode = constants.HTTP_OK
        response = {
            "public_key":
            user_id,
            "available_slots":
            self.users[user_id].get("available_slots"),
            "subscription_expiry":
            self.users[user_id].get("subscription_expiry"),
            "subscription_signature":
            Cryptographer.sign(registration_receipt, self.sk),
        }

        return response, rcode
예제 #27
0
def get_subscription_info(user_sk, teos_id, teos_url):
    """
    Gets information about a user's subscription status from the tower.

    Args:
        user_sk (:obj:`PrivateKey`): the user's private key.
        teos_id (:obj:`str`): the tower's compressed public key.
        teos_url (:obj:`str`): the teos base url.

    Returns:
        :obj:`dict`: A dictionary containing the subscription data, including number of remaining slots and the
            subscription's expiry time.

    Raises:
        :obj:`ConnectionError`: if the client cannot connect to the tower.
        :obj:`TowerResponseError`: if the tower responded with an error, or the response was invalid.
    """

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

    # Send request to the server.
    get_subscription_info_endpoint = "{}/get_subscription_info".format(
        teos_url)
    logger.info("Requesting subscription information from the Eye of Satoshi")
    response = process_post_response(
        post_request(data, get_subscription_info_endpoint))

    return response
예제 #28
0
def test_get_appointment_in_responder(api, client, generate_dummy_tracker,
                                      monkeypatch):
    # Mock the appointment in the Responder
    tracker = generate_dummy_tracker()
    track_data = AppointmentData(tracker=TrackerProto(
        locator=tracker.locator,
        dispute_txid=tracker.dispute_txid,
        penalty_txid=tracker.penalty_txid,
        penalty_rawtx=tracker.penalty_rawtx,
    ))
    status = AppointmentStatus.DISPUTE_RESPONDED
    monkeypatch.setattr(
        api.stub, "get_appointment",
        lambda x: GetAppointmentResponse(appointment_data=track_data,
                                         status=status))

    # Request it
    message = "get appointment {}".format(tracker.locator)
    signature = Cryptographer.sign(message.encode("utf-8"), user_sk)
    data = {"locator": tracker.locator, "signature": signature}
    r = client.post(get_appointment_endpoint, json=data)
    assert r.status_code == HTTP_OK

    # Check the received tracker data matches the mocked one
    assert tracker.locator == r.json.get("locator")
    assert tracker.dispute_txid == r.json.get("appointment").get(
        "dispute_txid")
    assert tracker.penalty_txid == r.json.get("appointment").get(
        "penalty_txid")
    assert tracker.penalty_rawtx == r.json.get("appointment").get(
        "penalty_rawtx")
예제 #29
0
def get_appointment(plugin, tower_id, locator):
    """
    Gets information about an appointment from the tower.

    Args:
        plugin (:obj:`Plugin`): this plugin.
        tower_id (:obj:`str`): the identifier of the tower to query.
        locator (:obj:`str`): the appointment locator.

    Returns:
        :obj:`dict`: a dictionary containing the appointment data.
    """

    # FIXME: All responses from the tower should be signed.
    try:
        tower_id, locator = arg_parser.parse_get_appointment_arguments(tower_id, locator)

        if tower_id not in plugin.wt_client.towers:
            raise InvalidParameter("tower_id is not within the registered towers", tower_id=tower_id)

        message = f"get appointment {locator}"
        signature = Cryptographer.sign(message.encode("utf-8"), plugin.wt_client.sk)
        data = {"locator": locator, "signature": signature}

        # Send request to the server.
        tower_netaddr = plugin.wt_client.towers[tower_id].netaddr
        get_appointment_endpoint = f"{tower_netaddr}/get_appointment"
        plugin.log(f"Requesting appointment from {tower_id}")

        response = process_post_response(post_request(data, get_appointment_endpoint, tower_id))
        return response

    except (InvalidParameter, TowerConnectionError, TowerResponseError) as e:
        plugin.log(str(e), level="warn")
        return e.to_json()
예제 #30
0
def test_get_subscription_info(api, client, monkeypatch):
    # MonkeyPatch the InternalAPI response (user being in the tower)
    available_slots = 42
    subscription_expiry = 1234
    appointments = [get_random_value_hex(32)]

    user_struct = Struct()
    user_struct.update({
        "subscription_expiry": subscription_expiry,
        "available_slots": available_slots,
        "appointments": appointments
    })
    monkeypatch.setattr(api.stub, "get_subscription_info",
                        lambda x: GetUserResponse(user=user_struct))

    # Request the data
    message = "get subscription info"
    signature = Cryptographer.sign(message.encode("utf-8"), user_sk)
    data = {"signature": signature}
    r = client.post(get_subscription_info_endpoint, json=data)

    # Check the data matches
    assert r.status_code == HTTP_OK
    assert r.get_json().get("available_slots") == available_slots
    assert r.get_json().get("subscription_expiry") == subscription_expiry
    assert r.get_json().get("appointments") == appointments