From 9cbd9ed18a083a1ea4479926fc49c177ea9a2447 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Tue, 14 Apr 2020 16:41:15 +0200 Subject: [PATCH] Adds end time to the user (subscription) information. It will replace the appointment one. --- teos/__init__.py | 1 + teos/api.py | 19 +++++++++++--- teos/gatekeeper.py | 62 ++++++++++++++++++++++++++++++++++++---------- teos/teosd.py | 7 +++++- teos/watcher.py | 7 +++--- 5 files changed, 75 insertions(+), 21 deletions(-) diff --git a/teos/__init__.py b/teos/__init__.py index b519ebc..1a29ddb 100644 --- a/teos/__init__.py +++ b/teos/__init__.py @@ -18,6 +18,7 @@ DEFAULT_CONF = { "FEED_PORT": {"value": 28332, "type": int}, "MAX_APPOINTMENTS": {"value": 1000000, "type": int}, "DEFAULT_SLOTS": {"value": 100, "type": int}, + "DEFAULT_SUBSCRIPTION_DURATION": {"value": 4320, "type": int}, "EXPIRY_DELTA": {"value": 6, "type": int}, "MIN_TO_SELF_DELAY": {"value": 20, "type": int}, "LOG_FILE": {"value": "teos.log", "type": str, "path": True}, diff --git a/teos/api.py b/teos/api.py index e5141bf..6bab385 100644 --- a/teos/api.py +++ b/teos/api.py @@ -130,8 +130,12 @@ class API: if client_pk: try: rcode = HTTP_OK - available_slots = self.gatekeeper.add_update_user(client_pk) - response = {"public_key": client_pk, "available_slots": available_slots} + available_slots, subscription_end_time = self.gatekeeper.add_update_user(client_pk) + response = { + "public_key": client_pk, + "available_slots": available_slots, + "subscription_end_time": subscription_end_time, + } except ValueError as e: rcode = HTTP_BAD_REQUEST @@ -204,7 +208,9 @@ class API: # DISCUSS: It may be worth using signals here to avoid race conditions anyway. self.gatekeeper.fill_slots(user_pk, required_slots) - appointment_added, signature = self.watcher.add_appointment(appointment, user_pk) + appointment_added, signature = self.watcher.add_appointment( + appointment, user_pk, self.gatekeeper.registered_users[user_pk].subscription_end_time + ) if appointment_added: # If the appointment is added and the update is smaller than the original, the difference is given back. @@ -215,7 +221,8 @@ class API: response = { "locator": appointment.locator, "signature": signature, - "available_slots": self.gatekeeper.registered_users[user_pk].get("available_slots"), + "available_slots": self.gatekeeper.registered_users[user_pk].available_slots, + "subscription_end_time": self.gatekeeper.registered_users[user_pk].subscription_end_time, } else: @@ -288,6 +295,8 @@ class API: appointment_data = self.watcher.db_manager.load_responder_tracker(uuid) if appointment_data: rcode = HTTP_OK + # Remove expiry field from appointment data since it is an internal field + appointment_data.pop("expiry") response = {"locator": locator, "status": "dispute_responded", "appointment": appointment_data} else: rcode = HTTP_NOT_FOUND @@ -298,6 +307,8 @@ class API: appointment_data = self.watcher.db_manager.load_watcher_appointment(uuid) if appointment_data: rcode = HTTP_OK + # Remove expiry field from appointment data since it is an internal field + appointment_data.pop("expiry") response = {"locator": locator, "status": "being_watched", "appointment": appointment_data} else: rcode = HTTP_NOT_FOUND diff --git a/teos/gatekeeper.py b/teos/gatekeeper.py index 6cf1f7b..baa21d8 100644 --- a/teos/gatekeeper.py +++ b/teos/gatekeeper.py @@ -20,19 +20,48 @@ class IdentificationFailure(Exception): pass +class UserInfo: + def __init__(self, available_slots, subscription_end_time, appointments=None): + self.available_slots = available_slots + self.subscription_end_time = subscription_end_time + + if not appointments: + self.appointments = {} + else: + self.appointments = appointments + + @classmethod + def from_dict(cls, user_data): + available_slots = user_data.get("available_slots") + appointments = user_data.get("appointments") + subscription_end_time = user_data.get("subscription_end_time") + + if any(v is None for v in [available_slots, appointments, subscription_end_time]): + raise ValueError("Wrong appointment data, some fields are missing") + + return cls(available_slots, subscription_expiry, appointments) + + def to_dict(self): + return self.__dict__ + + class Gatekeeper: """ The :class:`Gatekeeper` is in charge of managing the access to the tower. Only registered users are allowed to perform actions. Attributes: - registered_users (:obj:`dict`): a map of user_pk:appointment_slots. + registered_users (:obj:`dict`): a map of user_pk:UserInfo. """ - def __init__(self, user_db, default_slots): + def __init__(self, user_db, block_processor, default_slots, default_subscription_duration): self.default_slots = default_slots + self.block_processor = block_processor + self.default_subscription_duration = default_subscription_duration self.user_db = user_db - self.registered_users = user_db.load_all_users() + self.registered_users = { + user_id: UserInfo.from_dict(user_data) for user_id, user_data in user_db.load_all_users().items() + } def add_update_user(self, user_pk): """ @@ -42,20 +71,27 @@ class Gatekeeper: user_pk(:obj:`str`): the public key that identifies the user (33-bytes hex str). Returns: - :obj:`int`: the number of available slots in the user subscription. + :obj:`tuple`: a tuple with the number of available slots in the user subscription and the subscription end + time (in absolute block height). """ if not is_compressed_pk(user_pk): raise ValueError("Provided public key does not match expected format (33-byte hex string)") if user_pk not in self.registered_users: - self.registered_users[user_pk] = {"available_slots": self.default_slots} + self.registered_users[user_pk] = UserInfo( + self.default_slots, self.block_processor.get_block_count() + self.default_subscription_duration + ) else: - self.registered_users[user_pk]["available_slots"] += self.default_slots + # FIXME: For now new calls to register add default_slots to the current count and reset the expiry time + self.registered_users[user_pk].available_slots += self.default_slots + self.registered_users[user_pk].subscription_expiry = ( + self.block_processor.get_block_count() + self.default_subscription_duration + ) - self.user_db.store_user(user_pk, self.registered_users[user_pk]) + self.user_db.store_user(user_pk, self.registered_users[user_pk].to_dict()) - return self.registered_users[user_pk]["available_slots"] + return self.registered_users[user_pk].available_slots, self.registered_users[user_pk].subscription_end_time def identify_user(self, message, signature): """ @@ -97,9 +133,9 @@ class Gatekeeper: """ # DISCUSS: we may want to return a different exception if the user does not exist - if user_pk in self.registered_users and n <= self.registered_users.get(user_pk).get("available_slots"): - self.registered_users[user_pk]["available_slots"] -= n - self.user_db.store_user(user_pk, self.registered_users[user_pk]) + if user_pk in self.registered_users and n <= self.registered_users.get(user_pk).available_slots: + self.registered_users[user_pk].available_slots -= n + self.user_db.store_user(user_pk, self.registered_users[user_pk].to_dict()) else: raise NotEnoughSlots(user_pk, n) @@ -114,5 +150,5 @@ class Gatekeeper: # DISCUSS: if the user does not exist we may want to log or return an exception. if user_pk in self.registered_users: - self.registered_users[user_pk]["available_slots"] += n - self.user_db.store_user(user_pk, self.registered_users[user_pk]) + self.registered_users[user_pk].available_slots += n + self.user_db.store_user(user_pk, self.registered_users[user_pk].to_dict()) diff --git a/teos/teosd.py b/teos/teosd.py index 293fe52..03575d1 100644 --- a/teos/teosd.py +++ b/teos/teosd.py @@ -151,7 +151,12 @@ def main(command_line_conf): # Fire the API and the ChainMonitor # FIXME: 92-block-data-during-bootstrap-db chain_monitor.monitor_chain() - gatekeeper = Gatekeeper(UsersDBM(config.get("USERS_DB_PATH")), config.get("DEFAULT_SLOTS")) + gatekeeper = Gatekeeper( + UsersDBM(config.get("USERS_DB_PATH")), + block_processor, + config.get("DEFAULT_SLOTS"), + config.get("DEFAULT_SUBSCRIPTION_DURATION"), + ) inspector = Inspector(block_processor, config.get("MIN_TO_SELF_DELAY")) API(config.get("API_BIND"), config.get("API_PORT"), inspector, watcher, gatekeeper).start() except Exception as e: diff --git a/teos/watcher.py b/teos/watcher.py index 4107ad2..5f60fd3 100644 --- a/teos/watcher.py +++ b/teos/watcher.py @@ -95,7 +95,7 @@ class Watcher: """ return self.appointments.get(uuid) - def add_appointment(self, appointment, user_pk): + def add_appointment(self, appointment, user_pk, end_time): """ Adds a new appointment to the ``appointments`` dictionary if ``max_appointments`` has not been reached. @@ -114,6 +114,7 @@ class Watcher: appointment (:obj:`Appointment `): the appointment to be added to the :obj:`Watcher`. user_pk(:obj:`str`): the public key that identifies the user who sent the appointment (33-bytes hex str). + end_time (:obj:`int`): the block height where the tower will stop watching for breaches. Returns: :obj:`tuple`: A tuple signaling if the appointment has been added or not (based on ``max_appointments``). @@ -131,7 +132,7 @@ class Watcher: uuid = hash_160("{}{}".format(appointment.locator, user_pk)) self.appointments[uuid] = { "locator": appointment.locator, - "end_time": appointment.end_time, + "end_time": end_time, "size": len(appointment.encrypted_blob), } @@ -143,7 +144,7 @@ class Watcher: else: self.locator_uuid_map[appointment.locator] = [uuid] - self.db_manager.store_watcher_appointment(uuid, appointment.to_dict()) + self.db_manager.store_watcher_appointment(uuid, appointment.to_dict(), end_time) self.db_manager.create_append_locator_map(appointment.locator, uuid) appointment_added = True