Example #1
0
    def inspect(self, appointment_data):
        """
        Inspects whether the data provided by the user is correct.

        Args:
            appointment_data (:obj:`dict`): a dictionary containing the appointment data.

        Returns:
            :obj:`Appointment <common.appointment.Appointment>`: An appointment initialized with the provided data.

        Raises:
           :obj:`InspectionFailed`: if any of the fields is wrong.
        """

        if appointment_data is None:
            raise InspectionFailed(errors.APPOINTMENT_EMPTY_FIELD,
                                   "empty appointment received")
        elif not isinstance(appointment_data, dict):
            raise InspectionFailed(errors.APPOINTMENT_WRONG_FIELD,
                                   "wrong appointment format")

        self.check_locator(appointment_data.get("locator"))
        self.check_to_self_delay(appointment_data.get("to_self_delay"))
        self.check_blob(appointment_data.get("encrypted_blob"))

        return Appointment(
            appointment_data.get("locator"),
            appointment_data.get("encrypted_blob"),
            appointment_data.get("to_self_delay"),
        )
Example #2
0
def set_up_appointments(db_manager, total_appointments):
    appointments = dict()
    locator_uuid_map = dict()

    for i in range(total_appointments):
        uuid = uuid4().hex
        locator = get_random_value_hex(LOCATOR_LEN_BYTES)

        appointment = Appointment(locator, None, None, None, None)
        appointments[uuid] = {"locator": appointment.locator}
        locator_uuid_map[locator] = [uuid]

        db_manager.store_watcher_appointment(uuid, appointment.to_json())
        db_manager.create_append_locator_map(locator, uuid)

        # Each locator can have more than one uuid assigned to it.
        if i % 2:
            uuid = uuid4().hex

            appointments[uuid] = {"locator": appointment.locator}
            locator_uuid_map[locator].append(uuid)

            db_manager.store_watcher_appointment(uuid, appointment.to_json())
            db_manager.create_append_locator_map(locator, uuid)

    return appointments, locator_uuid_map
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)
Example #4
0
    def add_appointment(self, request, context):
        """Processes the request to add an appointment from a user."""
        try:
            appointment = Appointment(request.appointment.locator,
                                      request.appointment.encrypted_blob,
                                      request.appointment.to_self_delay)
            return AddAppointmentResponse(
                **self.watcher.add_appointment(appointment, request.signature))

        except (AuthenticationFailure, NotEnoughSlots):
            msg = "Invalid signature or user does not have enough slots available"
            status_code = grpc.StatusCode.UNAUTHENTICATED

        except AppointmentLimitReached:
            msg = "Appointment limit reached"
            status_code = grpc.StatusCode.RESOURCE_EXHAUSTED

        except SubscriptionExpired as e:
            msg = str(e)
            status_code = grpc.StatusCode.UNAUTHENTICATED

        except AppointmentAlreadyTriggered:
            msg = "The provided appointment has already been triggered"
            status_code = grpc.StatusCode.ALREADY_EXISTS

        except ConnectionRefusedError:
            msg = "Service unavailable"
            status_code = grpc.StatusCode.UNAVAILABLE

        context.set_details(msg)
        context.set_code(status_code)

        return AddAppointmentResponse()
def test_init_appointment(appointment_data):
    # The appointment has no checks whatsoever, since the inspector is the one taking care or that, and the only one
    # creating appointments.
    appointment = Appointment(appointment_data["locator"],
                              appointment_data["encrypted_blob"],
                              appointment_data["to_self_delay"])

    assert (appointment_data["locator"] == appointment.locator
            and appointment_data["to_self_delay"] == appointment.to_self_delay
            and appointment_data["encrypted_blob"]
            == appointment.encrypted_blob)
def test_to_dict(appointment_data):
    appointment = Appointment(appointment_data["locator"],
                              appointment_data["encrypted_blob"],
                              appointment_data["to_self_delay"])

    dict_appointment = appointment.to_dict()

    assert (appointment_data["locator"] == dict_appointment["locator"]
            and appointment_data["to_self_delay"]
            == dict_appointment["to_self_delay"]
            and appointment_data["encrypted_blob"]
            == dict_appointment["encrypted_blob"])
Example #7
0
def set_up_trackers(db_manager, total_trackers):
    trackers = dict()
    tx_tracker_map = dict()

    for i in range(total_trackers):
        uuid = uuid4().hex

        # We use the same txid for penalty and dispute here, it shouldn't matter
        penalty_txid = get_random_value_hex(32)
        dispute_txid = get_random_value_hex(32)
        locator = dispute_txid[:LOCATOR_LEN_HEX]

        # Appointment data
        appointment = Appointment(locator, None, None)

        # Store the data in the database and create a flag
        db_manager.store_watcher_appointment(uuid, appointment.to_dict())
        db_manager.create_triggered_appointment_flag(uuid)

        # Assign both penalty_txid and dispute_txid the same id (it shouldn't matter)
        tracker = TransactionTracker(locator, dispute_txid, penalty_txid, None,
                                     None)
        trackers[uuid] = {
            "locator": tracker.locator,
            "penalty_txid": tracker.penalty_txid
        }
        tx_tracker_map[penalty_txid] = [uuid]

        db_manager.store_responder_tracker(uuid, tracker.to_dict())

        # Each penalty_txid can have more than one uuid assigned to it.
        if i % 2:
            uuid = uuid4().hex

            trackers[uuid] = {
                "locator": tracker.locator,
                "penalty_txid": tracker.penalty_txid
            }
            tx_tracker_map[penalty_txid].append(uuid)

            db_manager.store_responder_tracker(uuid, tracker.to_dict())

            # Add them to the Watcher's db too
            db_manager.store_watcher_appointment(uuid, appointment.to_dict())
            db_manager.create_triggered_appointment_flag(uuid)

    return trackers, tx_tracker_map
Example #8
0
def test_to_json(appointment_data):
    appointment = Appointment(
        appointment_data["locator"],
        appointment_data["start_time"],
        appointment_data["end_time"],
        appointment_data["to_self_delay"],
        appointment_data["encrypted_blob"],
    )

    dict_appointment = json.loads(appointment.to_json())

    assert (appointment_data["locator"] == dict_appointment["locator"] and
            appointment_data["start_time"] == dict_appointment["start_time"]
            and appointment_data["end_time"] == dict_appointment["end_time"]
            and appointment_data["to_self_delay"]
            == dict_appointment["to_self_delay"]
            and EncryptedBlob(appointment_data["encrypted_blob"])
            == EncryptedBlob(dict_appointment["encrypted_blob"]))
Example #9
0
def test_init_appointment(appointment_data):
    # The appointment has no checks whatsoever, since the inspector is the one taking care or that, and the only one
    # creating appointments.
    # DISCUSS: whether this makes sense by design or checks should be ported from the inspector to the appointment
    #          35-appointment-checks
    appointment = Appointment(
        appointment_data["locator"],
        appointment_data["start_time"],
        appointment_data["end_time"],
        appointment_data["to_self_delay"],
        appointment_data["encrypted_blob"],
    )

    assert (appointment_data["locator"] == appointment.locator
            and appointment_data["start_time"] == appointment.start_time
            and appointment_data["end_time"] == appointment.end_time
            and appointment_data["to_self_delay"] == appointment.to_self_delay
            and EncryptedBlob(appointment_data["encrypted_blob"])
            == appointment.encrypted_blob)
Example #10
0
def on_commitment_revocation(plugin, **kwargs):
    """
    Sends an appointment to all registered towers for every net commitment transaction.

    kwargs should contain the commitment identifier (commitment_txid) and the penalty transaction (penalty_tx)

    Args:
        plugin (:obj:`Plugin`): this plugin.
    """

    try:
        commitment_txid, penalty_tx = arg_parser.parse_add_appointment_arguments(kwargs)
        appointment = Appointment(
            locator=compute_locator(commitment_txid),
            encrypted_blob=Cryptographer.encrypt(penalty_tx, commitment_txid),
            to_self_delay=20,  # does not matter for now, any value 20-2^32-1 would do
        )
        signature = Cryptographer.sign(appointment.serialize(), plugin.wt_client.sk)

    except (InvalidParameter, EncryptionError, SignatureError) as e:
        plugin.log(str(e), level="warn")
        return {"result": "continue"}

    # Send appointment to the towers.
    # FIXME: sending the appointment to all registered towers atm. Some management would be nice.
    for tower_id, tower in plugin.wt_client.towers.items():
        tower_update = {}

        if tower.status == "misbehaving":
            continue

        try:
            if tower.status == "reachable":
                tower_signature, available_slots = add_appointment(
                    plugin, tower_id, tower, appointment.to_dict(), signature
                )
                tower_update["appointment"] = (appointment.locator, tower_signature)
                tower_update["available_slots"] = available_slots

            else:
                if tower.status in ["temporarily unreachable", "unreachable"]:
                    plugin.log(f"{tower_id} is {tower.status}. Adding {appointment.locator} to pending")
                elif tower.status == "subscription error":
                    plugin.log(f"There is a subscription issue with {tower_id}. Adding appointment to pending")

                tower_update["pending_appointment"] = (appointment.to_dict(), signature), "add"

        except SignatureError as e:
            tower_update["status"] = "misbehaving"
            tower_update["misbehaving_proof"] = {
                "appointment": appointment.to_dict(),
                "signature": e.kwargs.get("signature"),
                "recovered_id": e.kwargs.get("recovered_id"),
                "receipt": e.kwargs.get("receipt"),
            }

        except TowerConnectionError:
            # All TowerConnectionError are transitory. Connections are tried on register so URLs cannot be malformed.
            # Flag appointment for retry
            tower_update["status"] = "temporarily unreachable"
            plugin.log(f"Adding {appointment.locator} to pending")
            tower_update["pending_appointment"] = (appointment.to_dict(), signature), "add"
            tower_update["retry"] = True

        except TowerResponseError as e:
            tower_update["status"] = e.kwargs.get("status")

            if tower_update["status"] in ["temporarily unreachable", "subscription error"]:
                plugin.log(f"Adding {appointment.locator} to pending")
                tower_update["pending_appointment"] = (appointment.to_dict(), signature), "add"

                if tower_update["status"] == "temporarily unreachable":
                    tower_update["retry"] = True

            if e.kwargs.get("invalid_appointment"):
                tower_update["invalid_appointment"] = (appointment.to_dict(), signature)

        finally:
            # Update memory and TowersDB
            plugin.wt_client.update_tower_state(tower_id, tower_update)

            if tower_update.get("retry"):
                plugin.wt_client.retrier.temp_unreachable_towers.put(tower_id)

    return {"result": "continue"}