Small fixes and docs

This commit is contained in:
Sergi Delgado Segura
2020-04-16 19:04:16 +02:00
parent a9b255e267
commit 8fad4d79cc
4 changed files with 71 additions and 20 deletions

View File

@@ -10,6 +10,7 @@ from teos.gatekeeper import NotEnoughSlots, AuthenticationFailure
from common.logger import Logger from common.logger import Logger
from common.cryptographer import hash_160 from common.cryptographer import hash_160
from common.exceptions import InvalidParameter
from common.constants import HTTP_OK, HTTP_BAD_REQUEST, HTTP_SERVICE_UNAVAILABLE, HTTP_NOT_FOUND from common.constants import HTTP_OK, HTTP_BAD_REQUEST, HTTP_SERVICE_UNAVAILABLE, HTTP_NOT_FOUND
@@ -48,7 +49,7 @@ def get_request_data_json(request):
:obj:`dict`: the dictionary parsed from the json request. :obj:`dict`: the dictionary parsed from the json request.
Raises: Raises:
:obj:`TypeError`: if the request is not json encoded or it does not decodes to a dictionary. :obj:`InvalidParameter`: if the request is not json encoded or it does not decodes to a dictionary.
""" """
if request.is_json: if request.is_json:
@@ -56,9 +57,9 @@ def get_request_data_json(request):
if isinstance(request_data, dict): if isinstance(request_data, dict):
return request_data return request_data
else: else:
raise TypeError("Invalid request content") raise InvalidParameter("Invalid request content")
else: else:
raise TypeError("Request is not json encoded") raise InvalidParameter("Request is not json encoded")
class API: class API:
@@ -112,7 +113,7 @@ class API:
try: try:
request_data = get_request_data_json(request) request_data = get_request_data_json(request)
except TypeError as e: except InvalidParameter as e:
logger.info("Received invalid register request", from_addr="{}".format(remote_addr)) logger.info("Received invalid register request", from_addr="{}".format(remote_addr))
return abort(HTTP_BAD_REQUEST, e) return abort(HTTP_BAD_REQUEST, e)
@@ -128,7 +129,7 @@ class API:
"subscription_expiry": subscription_expiry, "subscription_expiry": subscription_expiry,
} }
except ValueError as e: except InvalidParameter as e:
rcode = HTTP_BAD_REQUEST rcode = HTTP_BAD_REQUEST
error = "Error {}: {}".format(errors.REGISTRATION_MISSING_FIELD, str(e)) error = "Error {}: {}".format(errors.REGISTRATION_MISSING_FIELD, str(e))
response = {"error": error} response = {"error": error}
@@ -164,7 +165,7 @@ class API:
try: try:
request_data = get_request_data_json(request) request_data = get_request_data_json(request)
except TypeError as e: except InvalidParameter as e:
return abort(HTTP_BAD_REQUEST, e) return abort(HTTP_BAD_REQUEST, e)
try: try:
@@ -218,7 +219,7 @@ class API:
try: try:
request_data = get_request_data_json(request) request_data = get_request_data_json(request)
except TypeError as e: except InvalidParameter as e:
logger.info("Received invalid get_appointment request", from_addr="{}".format(remote_addr)) logger.info("Received invalid get_appointment request", from_addr="{}".format(remote_addr))
return abort(HTTP_BAD_REQUEST, e) return abort(HTTP_BAD_REQUEST, e)

View File

@@ -53,7 +53,15 @@ class Gatekeeper:
perform actions. perform actions.
Attributes: Attributes:
default_slots (:obj:`int`): the number of slots assigned to a user subscription.
default_subscription_duration (:obj:`int`): the expiry assigned to a user subscription.
expiry_delta (:obj:`int`): the grace period given to the user to renew their subscription.
block_processor (:obj:`BlockProcessor <teos.block_processor.BlockProcessor>`): a ``BlockProcessor`` instance to
get block from bitcoind.
user_db (:obj:`UserDBM <teos.user_dbm.UserDBM>`): a ``UserDBM`` instance to interact with the database.
registered_users (:obj:`dict`): a map of user_pk:UserInfo. registered_users (:obj:`dict`): a map of user_pk:UserInfo.
lock (:obj:`Lock`): a Threading.Lock object to lock access to the Gatekeeper on updates.
""" """
def __init__(self, user_db, block_processor, default_slots, default_subscription_duration, expiry_delta): def __init__(self, user_db, block_processor, default_slots, default_subscription_duration, expiry_delta):
@@ -75,12 +83,15 @@ class Gatekeeper:
user_pk(:obj:`str`): the public key that identifies the user (33-bytes hex str). user_pk(:obj:`str`): the public key that identifies the user (33-bytes hex str).
Returns: Returns:
:obj:`tuple`: a tuple with the number of available slots in the user subscription and the subscription end :obj:`tuple`: a tuple with the number of available slots in the user subscription and the subscription
time (in absolute block height). expiry (in absolute block height).
Raises:
:obj:`InvalidParameter`: if the user_pk does not match the expected format.
""" """
if not is_compressed_pk(user_pk): if not is_compressed_pk(user_pk):
raise ValueError("Provided public key does not match expected format (33-byte hex string)") raise InvalidParameter("Provided public key does not match expected format (33-byte hex string)")
if user_pk not in self.registered_users: if user_pk not in self.registered_users:
self.registered_users[user_pk] = UserInfo( self.registered_users[user_pk] = UserInfo(
@@ -125,11 +136,33 @@ class Gatekeeper:
raise AuthenticationFailure("Wrong message or signature.") raise AuthenticationFailure("Wrong message or signature.")
def update_available_slots(self, user_id, new_appointment, old_appointment=None): def update_available_slots(self, user_id, new_appointment, old_appointment=None):
"""
Updates (add/removes) slots from a user subscription.
Slots are removed if a new subscription is given, or an update is given with a new subscription bigger than the
old one.
Slots are added if an update is given but the new appointment is smaller than the old one.
Args:
user_id(:obj:`str`): the public key that identifies the user (33-bytes hex str).
new_appointment (:obj:`ExtendedAppointment <teos.extended_appointment.ExtendedAppointment>`): the new
appointment the user is requesting to add.
old_appointment (:obj:`ExtendedAppointment <teos.extended_appointment.ExtendedAppointment>`): the old
appointment the user wants to replace. Optional.
Returns:
:obj:`int`: the number of remaining appointment slots.
Raises:
:obj:`NotEnoughSlots`: If the user does not have enough slots to fill.
"""
self.lock.acquire() self.lock.acquire()
if old_appointment: if old_appointment:
# For updates the difference between the existing appointment and the update is computed. # For updates the difference between the existing appointment and the update is computed.
used_slots = ceil(new_appointment.get("size") / ENCRYPTED_BLOB_MAX_SIZE_HEX) used_slots = ceil(old_appointment.get("size") / ENCRYPTED_BLOB_MAX_SIZE_HEX)
required_slots = ceil(old_appointment.get("size") / ENCRYPTED_BLOB_MAX_SIZE_HEX) - used_slots required_slots = ceil(new_appointment.get("size") / ENCRYPTED_BLOB_MAX_SIZE_HEX) - used_slots
else: else:
# For regular appointments 1 slot is reserved per ENCRYPTED_BLOB_MAX_SIZE_HEX block. # For regular appointments 1 slot is reserved per ENCRYPTED_BLOB_MAX_SIZE_HEX block.
required_slots = ceil(new_appointment.get("size") / ENCRYPTED_BLOB_MAX_SIZE_HEX) required_slots = ceil(new_appointment.get("size") / ENCRYPTED_BLOB_MAX_SIZE_HEX)
@@ -145,8 +178,19 @@ class Gatekeeper:
self.lock.release() self.lock.release()
return self.registered_users.get(user_id).available_slots return self.registered_users.get(user_id).available_slots
def get_expiring_appointments(self, block_height): def get_expired_appointments(self, block_height):
expiring_appointments = [] """
Gets a list of appointments that are expiring at a given block height.
Args:
block_height: the block height that wants to be checked.
Returns:
:obj:`list`: a list of appointment uuids that will expire at ``block_height``.
"""
expired_appointments = []
for user_id, user_info in self.registered_users.items(): for user_id, user_info in self.registered_users.items():
if block_height > user_info.subscription_expiry + self.expiry_delta: if block_height == user_info.subscription_expiry + self.expiry_delta:
expiring_appointments.extend(user_info.appointments) expired_appointments.extend(user_info.appointments)
return expired_appointments

View File

@@ -116,6 +116,8 @@ class Responder:
is populated by the :obj:`ChainMonitor <teos.chain_monitor.ChainMonitor>`. is populated by the :obj:`ChainMonitor <teos.chain_monitor.ChainMonitor>`.
db_manager (:obj:`AppointmentsDBM <teos.appointments_dbm.AppointmentsDBM>`): a ``AppointmentsDBM`` instance db_manager (:obj:`AppointmentsDBM <teos.appointments_dbm.AppointmentsDBM>`): a ``AppointmentsDBM`` instance
to interact with the database. to interact with the database.
gatekeeper (:obj:`Gatekeeper <teos.gatekeeper.Gatekeeper>`): a `Gatekeeper` instance in charge to control the
user access and subscription expiry.
carrier (:obj:`Carrier <teos.carrier.Carrier>`): a ``Carrier`` instance to send transactions to bitcoind. carrier (:obj:`Carrier <teos.carrier.Carrier>`): a ``Carrier`` instance to send transactions to bitcoind.
block_processor (:obj:`BlockProcessor <teos.block_processor.BlockProcessor>`): a ``BlockProcessor`` instance to block_processor (:obj:`BlockProcessor <teos.block_processor.BlockProcessor>`): a ``BlockProcessor`` instance to
get data from bitcoind. get data from bitcoind.
@@ -394,7 +396,7 @@ class Responder:
""" """
expired_trackers = [ expired_trackers = [
uuid for uuid in self.gatekeeper.get_expired_appointment(height) if uuid in self.unconfirmed_txs uuid for uuid in self.gatekeeper.get_expired_appointments(height) if uuid in self.unconfirmed_txs
] ]
return expired_trackers return expired_trackers
@@ -402,7 +404,7 @@ class Responder:
def rebroadcast(self, txs_to_rebroadcast): def rebroadcast(self, txs_to_rebroadcast):
""" """
Rebroadcasts a ``penalty_tx`` that has missed too many confirmations. In the current approach this would loop Rebroadcasts a ``penalty_tx`` that has missed too many confirmations. In the current approach this would loop
forever if the transaction keeps not getting it. until the tracker expires if the penalty transactions keeps getting rejected due to fees.
Potentially, the fees could be bumped here if the transaction has some tower dedicated outputs (or allows it Potentially, the fees could be bumped here if the transaction has some tower dedicated outputs (or allows it
trough ``ANYONECANPAY`` or something similar). trough ``ANYONECANPAY`` or something similar).

View File

@@ -52,6 +52,8 @@ class Watcher:
populated by the :obj:`ChainMonitor <teos.chain_monitor.ChainMonitor>`. populated by the :obj:`ChainMonitor <teos.chain_monitor.ChainMonitor>`.
db_manager (:obj:`AppointmentsDBM <teos.appointments_dbm.AppointmentsDBM>`): a ``AppointmentsDBM`` instance db_manager (:obj:`AppointmentsDBM <teos.appointments_dbm.AppointmentsDBM>`): a ``AppointmentsDBM`` instance
to interact with the database. to interact with the database.
gatekeeper (:obj:`Gatekeeper <teos.gatekeeper.Gatekeeper>`): a `Gatekeeper` instance in charge to control the
user access and subscription expiry.
block_processor (:obj:`BlockProcessor <teos.block_processor.BlockProcessor>`): a ``BlockProcessor`` instance to block_processor (:obj:`BlockProcessor <teos.block_processor.BlockProcessor>`): a ``BlockProcessor`` instance to
get block from bitcoind. get block from bitcoind.
responder (:obj:`Responder <teos.responder.Responder>`): a ``Responder`` instance. responder (:obj:`Responder <teos.responder.Responder>`): a ``Responder`` instance.
@@ -129,7 +131,7 @@ class Watcher:
appointment_dict = {"locator": appointment.locator, "user_id": user_id, "size": len(appointment.encrypted_blob)} appointment_dict = {"locator": appointment.locator, "user_id": user_id, "size": len(appointment.encrypted_blob)}
available_slots = self.gatekeeper.update_available_slots(user_id, appointment_dict, self.appointments.get(uuid)) available_slots = self.gatekeeper.update_available_slots(user_id, appointment_dict, self.appointments.get(uuid))
self.gatekeeper.registered_users.appointments.append(uuid) self.gatekeeper.registered_users[user_id].appointments.append(uuid)
self.appointments[uuid] = appointment_dict self.appointments[uuid] = appointment_dict
if appointment.locator in self.locator_uuid_map: if appointment.locator in self.locator_uuid_map:
@@ -180,7 +182,9 @@ class Watcher:
if len(self.appointments) > 0 and block is not None: if len(self.appointments) > 0 and block is not None:
txids = block.get("tx") txids = block.get("tx")
expired_appointments = self.gatekeeper.get_expired_appointment(block["height"]) expired_appointments = self.gatekeeper.get_expired_appointments(block["height"])
# Make sure we only try to delete what is on the Watcher (some appointments may have been triggered)
expired_appointments = list(set(expired_appointments).intersection(self.appointments.keys()))
Cleaner.delete_expired_appointments( Cleaner.delete_expired_appointments(
expired_appointments, self.appointments, self.locator_uuid_map, self.db_manager expired_appointments, self.appointments, self.locator_uuid_map, self.db_manager