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.cryptographer import hash_160
from common.exceptions import InvalidParameter
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.
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:
@@ -56,9 +57,9 @@ def get_request_data_json(request):
if isinstance(request_data, dict):
return request_data
else:
raise TypeError("Invalid request content")
raise InvalidParameter("Invalid request content")
else:
raise TypeError("Request is not json encoded")
raise InvalidParameter("Request is not json encoded")
class API:
@@ -112,7 +113,7 @@ class API:
try:
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))
return abort(HTTP_BAD_REQUEST, e)
@@ -128,7 +129,7 @@ class API:
"subscription_expiry": subscription_expiry,
}
except ValueError as e:
except InvalidParameter as e:
rcode = HTTP_BAD_REQUEST
error = "Error {}: {}".format(errors.REGISTRATION_MISSING_FIELD, str(e))
response = {"error": error}
@@ -164,7 +165,7 @@ class API:
try:
request_data = get_request_data_json(request)
except TypeError as e:
except InvalidParameter as e:
return abort(HTTP_BAD_REQUEST, e)
try:
@@ -218,7 +219,7 @@ class API:
try:
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))
return abort(HTTP_BAD_REQUEST, e)

View File

@@ -53,7 +53,15 @@ class Gatekeeper:
perform actions.
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.
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):
@@ -75,12 +83,15 @@ class Gatekeeper:
user_pk(:obj:`str`): the public key that identifies the user (33-bytes hex str).
Returns:
:obj:`tuple`: a tuple with the number of available slots in the user subscription and the subscription end
time (in absolute block height).
:obj:`tuple`: a tuple with the number of available slots in the user subscription and the subscription
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):
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:
self.registered_users[user_pk] = UserInfo(
@@ -125,11 +136,33 @@ class Gatekeeper:
raise AuthenticationFailure("Wrong message or signature.")
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()
if old_appointment:
# 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)
required_slots = ceil(old_appointment.get("size") / ENCRYPTED_BLOB_MAX_SIZE_HEX) - used_slots
used_slots = ceil(old_appointment.get("size") / ENCRYPTED_BLOB_MAX_SIZE_HEX)
required_slots = ceil(new_appointment.get("size") / ENCRYPTED_BLOB_MAX_SIZE_HEX) - used_slots
else:
# 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)
@@ -145,8 +178,19 @@ class Gatekeeper:
self.lock.release()
return self.registered_users.get(user_id).available_slots
def get_expiring_appointments(self, block_height):
expiring_appointments = []
def get_expired_appointments(self, block_height):
"""
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():
if block_height > user_info.subscription_expiry + self.expiry_delta:
expiring_appointments.extend(user_info.appointments)
if block_height == user_info.subscription_expiry + self.expiry_delta:
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>`.
db_manager (:obj:`AppointmentsDBM <teos.appointments_dbm.AppointmentsDBM>`): a ``AppointmentsDBM`` instance
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.
block_processor (:obj:`BlockProcessor <teos.block_processor.BlockProcessor>`): a ``BlockProcessor`` instance to
get data from bitcoind.
@@ -394,7 +396,7 @@ class Responder:
"""
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
@@ -402,7 +404,7 @@ class Responder:
def rebroadcast(self, txs_to_rebroadcast):
"""
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
trough ``ANYONECANPAY`` or something similar).

View File

@@ -52,6 +52,8 @@ class Watcher:
populated by the :obj:`ChainMonitor <teos.chain_monitor.ChainMonitor>`.
db_manager (:obj:`AppointmentsDBM <teos.appointments_dbm.AppointmentsDBM>`): a ``AppointmentsDBM`` instance
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
get block from bitcoind.
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)}
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
if appointment.locator in self.locator_uuid_map:
@@ -180,7 +182,9 @@ class Watcher:
if len(self.appointments) > 0 and block is not None:
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(
expired_appointments, self.appointments, self.locator_uuid_map, self.db_manager