mirror of
https://github.com/aljazceru/python-teos.git
synced 2025-12-18 06:34:19 +01:00
Small fixes and docs
This commit is contained in:
15
teos/api.py
15
teos/api.py
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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).
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user